Data governance is often treated as an after-thought feature applied reactively when compliance audits approach. In Snowflake, you can embed governance directly into your data model, ensuring security and compliance are applied inline with the data itself.
In this article, I’ll walk through how to implement a SQL-based governance framework using tags, masking policies, and role-based access. By the end, you’ll see how data visibility dynamically changes based on roles, without needing to manually update policies table-by-table.
Why Governance Matters
Data governance ensures that the right people have access to the right data at the right time without overexposing sensitive or irrelevant information. Without governance, organizations risk compliance violations, data leaks, and reduced trust in analytics. By applying governance policies directly within Snowflake, companies can enforce fine-grained access control at the column or row level, keeping data secure while still enabling collaboration.
Use Case: Company XYZ
Company XYZ has multiple departments; Marketing, Compliance, Finance, and Engineering each requiring access to specific data sets. With Governance:
- Marketing only sees campaign performance and customer engagement data.
- Compliance has visibility into audit logs and regulatory reports.
- Finance can access revenue, transactions, and cost data but not engineering roadmaps or marketing leads.
- Engineering works with operational metrics without being exposed to financial or compliance-sensitive details.
This prevents unnecessary overexposure of data across teams, while still maintaining a single source of truth in Snowflake. Governance not only improves security but also streamlines collaboration by ensuring teams work with relevant, trusted data.
Dynamic Data Masking
Dynamic Data Masking is a Column-level Security feature that uses masking policies to selectively mask plain-text data in table and view columns at query time. Masking policies can include conditions and functions to transform the data at query runtime when those conditions are met.
Step 1: Create Setup Workspace
-- create database and schema
CREATE DATABASE RAW;
USE DATABASE RAW;
CREATE SCHEMA COMPANY_WIDE;
-- simulate data
CREATE OR REPLACE TABLE RAW.COMPANY_WIDE.CUSTOMER_DATA (
CUSTOMER_ID INT,
FULL_NAME STRING,
EMAIL STRING,
PHONE STRING,
CREDIT_CARD STRING,
PURCHASE_AMOUNT NUMBER(10,2),
REGION STRING,
COMPLIANCE_FLAG STRING
);
INSERT INTO RAW.COMPANY_WIDE.CUSTOMER_DATA VALUES
(1, 'Alice Johnson', '[email protected]', '+1-202-555-0181', '4111111111111111', 2500.50, 'US', 'GDPR'),
(2, 'Bob Smith', '[email protected]', '+44-20-7946-0958', '5500000000000004', 780.75, 'EU', 'GDPR'),
(3, 'Clara Green', '[email protected]', '+81-3-1234-5678', '340000000000009', 12000.00, 'APAC', 'PCI'),
(4, 'David Lee', '[email protected]', '+1-415-555-1212', '30000000000004', 6500.90, 'US', 'HIPAA');
-- Department roles
CREATE ROLE IF NOT EXISTS MARKETING_ACCESS;
CREATE ROLE IF NOT EXISTS FINANCIAL_ACCESS;
CREATE ROLE IF NOT EXISTS REGULATORY_ACCESS;
CREATE ROLE IF NOT EXISTS ENGINEERING_ACCESS;
-- Assign roles to a warehouse and schema
GRANT USAGE ON WAREHOUSE COMPUTE_WH TO MARKETING_ACCESS;
GRANT USAGE ON SCHEMA RAW.COMPANY_WIDE TO MARKETING_ACCESS;
GRANT USAGE ON WAREHOUSE COMPUTE_WH TO FINANCIAL_ACCESS;
GRANT USAGE ON SCHEMA RAW.COMPANY_WIDE TO FINANCIAL_ACCESS;
GRANT USAGE ON WAREHOUSE COMPUTE_WH TO REGULATORY_ACCESS;
GRANT USAGE ON SCHEMA RAW.COMPANY_WIDE TO REGULATORY_ACCESS;
GRANT USAGE ON WAREHOUSE COMPUTE_WH TO ENGINEERING_ACCESS;
GRANT USAGE ON SCHEMA RAW.COMPANY_WIDE TO ENGINEERING_ACCESS;
Step 2: Create Governance Tag
We start by creating a governance tag that captures sensitivity categories.
-- Create governance tag with allowed values
CREATE TAG IF NOT EXISTS RAW.COMPANY_WIDE.MASKING_TAG
ALLOWED_VALUES 'MARKETING', 'SENSITIVE', 'FINANCIAL', 'REGULATORY', 'ENGINEERING';
Step 3: Define Masking Policies
Masking policies enforce role-based visibility. Let’s create two policies: one for strings, another for numbers.
-- Masking policy for string values
CREATE OR REPLACE MASKING POLICY RAW.COMPANY_WIDE.MASK_STRING
AS (VAL STRING) RETURNS STRING ->
CASE
-- MARKETING role: can see PII (SENSITIVE) + REGION
WHEN CURRENT_ROLE() = 'MARKETING_ACCESS'
AND SYSTEM$GET_TAG_ON_CURRENT_COLUMN('RAW.COMPANY_WIDE.MASKING_TAG') = 'SENSITIVE'
THEN VAL
-- FINANCIAL role: can see FINANCIAL (CREDIT_CARD, PURCHASE_AMOUNT)
WHEN CURRENT_ROLE() = 'FINANCIAL_ACCESS'
AND SYSTEM$GET_TAG_ON_CURRENT_COLUMN('RAW.COMPANY_WIDE.MASKING_TAG') = 'FINANCIAL'
THEN VAL
-- REGULATORY role: can see COMPLIANCE_FLAG
WHEN CURRENT_ROLE() = 'REGULATORY_ACCESS'
AND SYSTEM$GET_TAG_ON_CURRENT_COLUMN('RAW.COMPANY_WIDE.MASKING_TAG') = 'REGULATORY'
THEN VAL
-- FOR HIGER ROLES
WHEN CURRENT_ROLE() NOT IN ('MARKETING_ACCESS', 'FINANCIAL_ACCESS', 'REGULATORY_ACCESS', 'ENGINEERING_ACCESS')
THEN VAL
-- REGION is not tagged, ENGINEERING
WHEN SYSTEM$GET_TAG_ON_CURRENT_COLUMN('RAW.COMPANY_WIDE.MASKING_TAG') IS NULL
THEN VAL
-- Default mask
ELSE '*****'
END;
-- Masking policy for number values
CREATE OR REPLACE MASKING POLICY RAW.COMPANY_WIDE.MASK_NUMBER
AS (VAL NUMBER) RETURNS NUMBER ->
CASE
-- FINANCIAL role: can see FINANCIAL data
WHEN CURRENT_ROLE() = 'FINANCIAL_ACCESS'
AND SYSTEM$GET_TAG_ON_CURRENT_COLUMN('RAW.COMPANY_WIDE.MASKING_TAG') = 'FINANCIAL'
THEN VAL
-- REGULATORY role: can see COMPLIANCE_FLAG
WHEN CURRENT_ROLE() = 'REGULATORY_ACCESS'
AND SYSTEM$GET_TAG_ON_CURRENT_COLUMN('RAW.COMPANY_WIDE.MASKING_TAG') = 'REGULATORY'
THEN VAL
-- untagged
WHEN SYSTEM$GET_TAG_ON_CURRENT_COLUMN('RAW.COMPANY_WIDE.MASKING_TAG') IS NULL
THEN VAL
-- FOR HIGER ROLES
WHEN CURRENT_ROLE() NOT IN ('MARKETING_ACCESS', 'FINANCIAL_ACCESS', 'REGULATORY_ACCESS', 'ENGINEERING_ACCESS')
THEN VAL
-- Default mask
ELSE 0
END;
Step 4: Attach Policies to Tags
Instead of attaching policies column by column, we bind them directly to the tag.
ALTER TAG RAW.COMPANY_WIDE.MASKING_TAG
SET MASKING POLICY RAW.COMPANY_WIDE.MASK_STRING,
MASKING POLICY RAW.COMPANY_WIDE.MASK_NUMBER;
Now, any column tagged with MASKING_TAG automatically inherits the correct masking behaviour.
Step 5: Attach Tags to Columns
Now we can apply these tags inline to any table’s columns.
-- Attach tags to specific columns
-- Customer PII (Marketing relevance)
ALTER TABLE RAW.COMPANY_WIDE.CUSTOMER_DATA
MODIFY COLUMN FULL_NAME SET TAG RAW.COMPANY_WIDE.MASKING_TAG = 'SENSITIVE';
ALTER TABLE RAW.COMPANY_WIDE.CUSTOMER_DATA
MODIFY COLUMN EMAIL SET TAG RAW.COMPANY_WIDE.MASKING_TAG = 'SENSITIVE';
-- Financial data
ALTER TABLE RAW.COMPANY_WIDE.CUSTOMER_DATA
MODIFY COLUMN CREDIT_CARD SET TAG RAW.COMPANY_WIDE.MASKING_TAG = 'FINANCIAL';
ALTER TABLE RAW.COMPANY_WIDE.CUSTOMER_DATA
MODIFY COLUMN PURCHASE_AMOUNT SET TAG RAW.COMPANY_WIDE.MASKING_TAG = 'FINANCIAL';
-- Compliance-specific data
ALTER TABLE RAW.COMPANY_WIDE.CUSTOMER_DATA
MODIFY COLUMN COMPLIANCE_FLAG SET TAG RAW.COMPANY_WIDE.MASKING_TAG = 'REGULATORY';
-- Region column left untagged
These tags become metadata that Snowflake’s governance engine can read dynamically.
Step 5. Governance in Action
Let’s see what this looks like in practice.
Governance Relevance per Department
- Marketing: Needs access to FULL_NAME, EMAIL, REGION, but not sensitive financial or compliance fields.
- Finance: Needs access to CREDIT_CARD, PURCHASE_AMOUNT, but not personal contact details.
- Compliance: Needs access to COMPLIANCE_FLAG, REGION, but customer identifiers should be masked.
- Engineering: Needs synthetic test data (non-sensitive), so most fields should be masked.
USE ROLE MARKETING_ACCESS;
SELECT FULL_NAME, EMAIL, REGION, CREDIT_CARD, PURCHASE_AMOUNT, COMPLIANCE_FLAG
FROM RAW.COMPANY_WIDE.CUSTOMER_DATA;
-- FULL_NAME + EMAIL + REGION visible
-- CREDIT_CARD + PURCHASE_AMOUNT + COMPLIANCE_FLAG masked
USE ROLE FINANCIAL_ACCESS;
SELECT FULL_NAME, EMAIL, REGION, CREDIT_CARD, PURCHASE_AMOUNT, COMPLIANCE_FLAG
FROM RAW.COMPANY_WIDE.CUSTOMER_DATA;
-- CREDIT_CARD + PURCHASE_AMOUNT visible
-- EMAIL + FULL_NAME + COMPLIANCE_FLAG masked
USE ROLE REGULATORY_ACCESS;
SELECT FULL_NAME, EMAIL, REGION, CREDIT_CARD, PURCHASE_AMOUNT, COMPLIANCE_FLAG
FROM RAW.COMPANY_WIDE.CUSTOMER_DATA;
-- COMPLIANCE_FLAG + REGION visible
-- FULL_NAME, EMAIL, CREDIT_CARD, PURCHASE_AMOUNT masked
USE ROLE ENGINEERING_ACCESS;
SELECT FULL_NAME, EMAIL, REGION, CREDIT_CARD, PURCHASE_AMOUNT, COMPLIANCE_FLAG
FROM RAW.COMPANY_WIDE.CUSTOMER_DATA;
-- Most fields masked
-- Possibly REGION partially visible for non-sensitive testing
With no extra code, Snowflake enforces the right level of visibility based on the user’s active role.
Step 7. Clean-Up
For demos or testing environments, you can reset your environment with:
-- remove policy from tags
ALTER TAG RAW.COMPANY_WIDE.MASKING_TAG
UNSET MASKING POLICY RAW.COMPANY_WIDE.MASK_STRING,
MASKING POLICY RAW.COMPANY_WIDE.MASK_NUMBER;
-- drop policy
DROP MASKING POLICY RAW.COMPANY_WIDE.MASK_STRING;
DROP MASKING POLICY RAW.COMPANY_WIDE.MASK_NUMBER;
-- drop tag
DROP TAG RAW.COMPANY_WIDE.MASKING_TAG;
Conclusion
Governance in Snowflake transforms compliance from a manual burden into a built-in data feature. By combining tags, masking policies, and roles, you can:
- Classify sensitive data directly at the schema level.
- Enforce access dynamically, without rewriting queries.
- Apply governance consistently across multiple tables using procedures.
This approach ensures your data remains both useful and compliant scaling with your organization instead of slowing it down.