Trust Center and RBAC

Snowflake's authorization model is pure RBAC: privileges are granted to roles, roles are granted to users (or other roles), and a session activates one primary role plus optional secondaries. Trust Center sits on top of this as a continuously running scanner that reads the metadata layer and flags configuration drift — a user with no MFA, a role with MANAGE GRANTS outside SECURITYADMIN, an account-level network policy that allows 0.0.0.0/0. Together they form the configure once, audit forever half of the security stack.


1. Trust Center

Trust Center is a Snowsight surface that runs scanner packages against the account's metadata. Snowflake ships built-in packages (CIS Benchmark, threat detection); customers can register custom scanners as SQL UDFs that return a row per finding.


USE ROLE ACCOUNTADMIN;

-- Enable the CIS Benchmark scanner package
CALL SNOWFLAKE.TRUST_CENTER.ENABLE_PACKAGE('CIS_BENCHMARKS');

-- Schedule scans every 24 hours
CALL SNOWFLAKE.TRUST_CENTER.UPDATE_PACKAGE(
  'CIS_BENCHMARKS',
  {'schedule_interval_hours': 24}
);

-- Read the latest findings
SELECT scanner_name, severity, entity_name, finding, recommendation
FROM   SNOWFLAKE.TRUST_CENTER.FINDINGS
WHERE  status = 'OPEN'
ORDER  BY CASE severity
            WHEN 'CRITICAL' THEN 1
            WHEN 'HIGH'     THEN 2
            WHEN 'MEDIUM'   THEN 3
            ELSE 4
          END;

Typical CIS findings: users without MFA, roles granted directly to users (vs through a role hierarchy), ACCOUNTADMIN granted to humans, network policy permitting public traffic, no session policy, replication enabled to an unauthorized account.

2. Role Hierarchy

The diagram shows the canonical hierarchy with system roles at the top and custom roles fanning out below. Privileges flow upward: a role inherits everything its child roles have. Users always activate one role at a time (plus secondaries); the active role plus the inherited tree is the effective privilege set.

┌──────────────────────────────────────────────────────────────────────┐
│               SNOWFLAKE ROLE HIERARCHY                               │
│                                                                      │
│                       ┌──────────────────┐                           │
│                       │   ACCOUNTADMIN   │  (top of tree)            │
│                       └────────┬─────────┘                           │
│                                │ inherits                            │
│              ┌─────────────────┼─────────────────┐                   │
│              ▼                 ▼                 ▼                   │
│     ┌──────────────────┐ ┌────────────┐ ┌────────────────┐           │
│     │   SECURITYADMIN  │ │ SYSADMIN   │ │ ORGADMIN       │           │
│     └────────┬─────────┘ └─────┬──────┘ │ (org-wide)     │           │
│              │                 │        └────────────────┘           │
│              ▼                 ▼                                     │
│     ┌──────────────────┐ ┌──────────────────────────┐                │
│     │    USERADMIN     │ │  Custom functional roles │                │
│     └──────────────────┘ │  ETL_RW / ANALYST_RO …   │                │
│                          └─────────────┬────────────┘                │
│                                        │                             │
│                                        ▼                             │
│                          ┌──────────────────────────┐                │
│                          │  Custom access roles     │                │
│                          │  DB_X_RO / DB_X_RW       │                │
│                          └─────────────┬────────────┘                │
│                                        │                             │
│                                        ▼                             │
│                          ┌──────────────────────────┐                │
│                          │   PUBLIC (everyone)      │                │
│                          └──────────────────────────┘                │
└──────────────────────────────────────────────────────────────────────┘

3. System Roles

4. Custom Role Patterns

The two-tier pattern (functional roles + access roles) is the standard. Functional roles describe what someone does (analyst, ETL job, BI dashboard); access roles describe what they can touch (RW on DB X, RO on schema Y). Functional roles inherit one or more access roles, never object privileges directly.


USE ROLE SECURITYADMIN;

-- Access roles (one pair per database, optionally per schema)
CREATE ROLE analytics_db_ro;
CREATE ROLE analytics_db_rw;

GRANT USAGE ON DATABASE analytics                  TO ROLE analytics_db_ro;
GRANT USAGE ON ALL SCHEMAS IN DATABASE analytics   TO ROLE analytics_db_ro;
GRANT SELECT ON ALL TABLES IN DATABASE analytics   TO ROLE analytics_db_ro;
GRANT SELECT ON ALL VIEWS  IN DATABASE analytics   TO ROLE analytics_db_ro;

GRANT USAGE       ON DATABASE analytics                TO ROLE analytics_db_rw;
GRANT USAGE       ON ALL SCHEMAS IN DATABASE analytics TO ROLE analytics_db_rw;
GRANT SELECT, INSERT, UPDATE, DELETE
                  ON ALL TABLES IN DATABASE analytics  TO ROLE analytics_db_rw;

-- Functional roles
CREATE ROLE analyst_ro;
CREATE ROLE etl_loader;
CREATE ROLE bi_dashboard;

GRANT ROLE analytics_db_ro TO ROLE analyst_ro;
GRANT ROLE analytics_db_rw TO ROLE etl_loader;
GRANT ROLE analytics_db_ro TO ROLE bi_dashboard;

-- Wire functional roles into the SYSADMIN tree
GRANT ROLE analyst_ro   TO ROLE sysadmin;
GRANT ROLE etl_loader   TO ROLE sysadmin;
GRANT ROLE bi_dashboard TO ROLE sysadmin;

-- Assign to users
GRANT ROLE analyst_ro TO USER alice;
GRANT ROLE etl_loader TO USER svc_etl;

5. Future Grants

A normal grant applies only to existing objects. A future grant applies to objects created later. This is the only way to keep a freshly created table inside a schema readable by the right role without a follow-up grant DDL on every object.


-- Every future table in the schema is readable by analytics_db_ro
GRANT SELECT ON FUTURE TABLES IN SCHEMA analytics.public
  TO ROLE analytics_db_ro;

GRANT SELECT ON FUTURE VIEWS  IN SCHEMA analytics.public
  TO ROLE analytics_db_ro;

-- Inspect future grants
SHOW FUTURE GRANTS IN SCHEMA analytics.public;

-- Schema-level future grants are evaluated when objects are created.
-- Database-level future grants override schema-level for the same privilege.
GRANT SELECT ON FUTURE TABLES IN DATABASE analytics
  TO ROLE analytics_db_ro;

Future grants are how DataOps teams keep "the analytics warehouse can read everything in the analytics database" true forever. Without them, every CREATE TABLE needs a follow-up GRANT and the gap between the two is when human "I can't see the new table" tickets get filed.

6. Auditing via ACCESS_HISTORY

SNOWFLAKE.ACCOUNT_USAGE.ACCESS_HISTORY records every column read by every query. It is the source of truth for "who looked at this PII column last quarter" and the input to most data-loss-prevention controls.


-- Who has touched the customers.ssn column in the last 30 days?
SELECT user_name,
       COUNT(*)                                AS reads,
       MIN(query_start_time)                   AS first_read,
       MAX(query_start_time)                   AS last_read
FROM   SNOWFLAKE.ACCOUNT_USAGE.ACCESS_HISTORY,
       LATERAL FLATTEN(input => direct_objects_accessed) o,
       LATERAL FLATTEN(input => o.value:columns) c
WHERE  o.value:objectName::STRING = 'ANALYTICS.PUBLIC.CUSTOMERS'
  AND  c.value:columnName::STRING = 'SSN'
  AND  query_start_time >= DATEADD('day', -30, CURRENT_TIMESTAMP())
GROUP  BY user_name
ORDER  BY reads DESC;

-- Find roles that have been granted to humans directly (anti-pattern)
SELECT u.name AS user_name, r.role
FROM   SNOWFLAKE.ACCOUNT_USAGE.GRANTS_TO_USERS r
JOIN   SNOWFLAKE.ACCOUNT_USAGE.USERS u ON u.name = r.grantee_name
WHERE  r.role IN ('ACCOUNTADMIN', 'SECURITYADMIN', 'ORGADMIN')
  AND  r.deleted_on IS NULL
  AND  u.type != 'SERVICE';

-- Detect role-grant changes (who granted what, to whom, when)
SELECT created_on, granted_by, grantee_name, role, granted_on
FROM   SNOWFLAKE.ACCOUNT_USAGE.GRANTS_TO_ROLES
WHERE  created_on >= DATEADD('day', -7, CURRENT_TIMESTAMP())
ORDER  BY created_on DESC;

A useful pattern: pipe ACCESS_HISTORY deltas into a Snowpipe-fed downstream table that retains beyond the 365-day ACCOUNT_USAGE window, then run weekly anomaly detection on it (queries from new IPs, role escalations, off-hours access to RESTRICTED data). This is how Trust Center findings turn into actionable alerts instead of dashboard wallpaper.


↑ Back to Top