RBAC + DAC in Snowflake
Domain 2 starts here, carrying 20% of the COF-C03 exam across nine days, and Day 18 lays the foundation. The two access-control models Snowflake runs side by side are RBAC (privileges go to roles, roles go to users) and DAC (each object has an owner role that grants access on it), with the official C03 sample exam Q4 wired directly to DAC.
| Term | Plain meaning |
|---|---|
| Access control | The rules that decide who can do what to which objects. Snowflake combines DAC + RBAC (and optionally UBAC). |
| Securable object | Anything you can grant or revoke privileges on: database, schema, table, warehouse, role. Access is denied by default. Nothing is implicit. |
| Role | A named bundle of privileges. Roles are granted to users (and to other roles). In Snowflake, the unit of permission is the role, not the user. |
| Privilege | A specific permitted action on an object. SELECT on a table, USAGE on a schema, OPERATE on a warehouse. |
| Owner | The role that holds the OWNERSHIP privilege on an object. Roles own objects. Users do not. |
| RBAC | Role-Based Access Control. Privileges → Roles → Users. The model Snowflake uses for scalable production access. |
| DAC | Discretionary Access Control. The object’s owner role can grant access on it at its own discretion. Runs alongside RBAC. |
| UBAC | User-Based Access Control. Privileges granted directly to a user. Effective only when USE SECONDARY ROLES = ALL. Narrow use case. |
Today’s Concept
Micro-Concept 1: The Two Sentences That Carry Half the Domain
Two sentences answer most Domain 2 access-control questions on their own.
RBAC: “Privileges are granted to roles, and roles are granted to users.”
DAC: “Each object has an owner role that can grant access on it.”
Snowflake runs both at the same time. They are not alternatives. RBAC is the assignment model. DAC is the ownership model. Every object you create has an owner role (DAC), and every privilege on that object is delivered through a role grant (RBAC).
Sample Q4 from the official C03 study guide reads, paraphrased: “Which Snowflake access control framework allows the owner of an object to grant access to other users?” The answer is Discretionary Access Control (DAC). The cue word is “owner.” If the stem says “owner grants access,” the answer is DAC. If the stem says “privileges assigned to groups of users,” the answer is RBAC. Read for the cue, then commit.
Micro-Concept 2: The Two-Step Privilege Flow (RBAC)
RBAC always flows in exactly two steps. Multi-select questions love to slip a third step into the options or merge the two into a single “grant directly to user” line. Both are wrong.
| Step | Statement | Example SQL |
|---|---|---|
| 1 | Grant the privilege to a role. | GRANT SELECT ON TABLE sales.orders TO ROLE analyst_r; |
| 2 | Grant the role to a user (or to another role). | GRANT ROLE analyst_r TO USER alice; |
Privileges are never granted directly to users in RBAC. That pattern is UBAC, which is a separate model with narrow scope (covered below). An exam option that says “in RBAC, privileges are granted to users” is a guaranteed wrong answer.
Roles can also be granted to other roles. That is how the role hierarchy works:
GRANT ROLE analyst_r TO ROLE SYSADMIN;
-- Anyone using SYSADMIN now also has every privilege analyst_r holds.
This is privilege inheritance. A parent role inherits every privilege of every child role granted to it. That is why ACCOUNTADMIN, sitting at the top of the system-role chain (ACCOUNTADMIN → SECURITYADMIN & SYSADMIN → USERADMIN → PUBLIC), can see and operate on everything inside its inheritance tree. The chain matters. We cover the system roles in detail on Day 19.
Micro-Concept 3: DAC, Ownership Belongs to a Role
The single most-missed point on DAC: roles own objects, not users. The role that is active in the session when CREATE runs becomes the owner. That ownership is the OWNERSHIP privilege, and it can be transferred to another role with a single grant.
USE ROLE analyst_r;
CREATE TABLE my_data (id INT);
-- analyst_r now owns my_data. analyst_r can grant SELECT on it to other roles.
-- Transfer ownership to a different role:
GRANT OWNERSHIP ON TABLE my_data TO ROLE data_steward_r;
-- analyst_r loses ownership; data_steward_r gains it.
Three consequences fall out of this rule, and exam stems exploit each of them:
- If Alice and Bob both have
analyst_rgranted to them, they share ownership of anythinganalyst_rcreates. Ownership belongs to the role; the role belongs to both of them. - If Alice leaves and her account is dropped, the table she created is not orphaned.
analyst_rstill owns it. Anyone else holdinganalyst_rcan still manage it. - The owner role can grant any privilege on its object to any other role without involving ACCOUNTADMIN. That is the “discretionary” in DAC. The owner role decides on its own.
On a recent client migration, this is exactly why we never bind ownership to a personal user. Ownership tracks a service role. People rotate off projects; roles persist.
Micro-Concept 4: The Securable Object Hierarchy
Privileges are not standalone. They flow through a hierarchy of containers. Reading a table needs three privileges, not one.
| Container level | Privilege needed | Why |
|---|---|---|
| Database | USAGE | Without it, the database is invisible. |
| Schema | USAGE | Without it, you see the database but none of its contents. |
| Table | SELECT | The read permission itself. Useless without USAGE on the levels above. |
The required order is Database USAGE → Schema USAGE → Table SELECT. Outermost first, innermost last. Exam multi-selects will offer combinations like “schema SELECT” or “table USAGE.” Both are nonsense (tables do not have USAGE, schemas do not have SELECT), but they trip people up when the options are listed quickly.
The grant pattern looks like this:
GRANT USAGE ON DATABASE sales_db TO ROLE analyst_r;
GRANT USAGE ON SCHEMA sales_db.public TO ROLE analyst_r;
GRANT SELECT ON TABLE sales_db.public.orders TO ROLE analyst_r;
Two shortcuts worth knowing. GRANT <priv> ON ALL ... IN SCHEMA covers objects that already exist. GRANT <priv> ON FUTURE ... IN SCHEMA covers objects created later. The future-grant pattern is what production teams use to onboard new tables automatically without manually re-granting on every new object.
Micro-Concept 5: Account Roles vs Database Roles
Until a few years ago, every role in Snowflake was account-wide. Snowflake now distinguishes two role types, and C03 tests the difference directly.
| Account role | Database role | |
|---|---|---|
| Scope | Whole account. Privileges on any object. | One database. Privileges only on objects inside it. |
| Created with | CREATE ROLE my_role; | CREATE DATABASE ROLE my_db.my_role; |
| Can be activated in a session? | Yes. USE ROLE my_role; | No. Must be granted to an account role and used through it. |
| Granted to | Users or other roles | Account roles (or other database roles in the same database) |
| Lifecycle | Independent of any database | Tied to the database. Dropped when the database is dropped. |
Database roles exist so a database owner can manage access for their database without touching the account-wide role hierarchy. A common pattern: define a database role db_reader with USAGE on the database, USAGE on every schema, and SELECT on every table. Then grant that one database role to whichever account roles need to query the database, for example an analyst_r for one team and a reporting_r for another. The database owner publishes a single, well-scoped read role. The account hierarchy stays clean.
The trap that lands on the exam: an option claiming you can activate a database role directly with USE ROLE my_db.my_db_role. That is false. Database roles are only usable through an account role they have been granted to. In SnowPro training sessions I have run, this is consistently the question where the room splits 50/50 on the first attempt.
Micro-Concept 6: PUBLIC, the Default Floor
PUBLIC is a special pseudo-role with four properties:
- Every user is automatically granted
PUBLIC. No explicit grant is needed. - Every role automatically inherits
PUBLIC. PUBLICcannot be dropped.- Anything granted to
PUBLICis effectively granted to every user in the account.
Used deliberately, PUBLIC is the right place for grants that genuinely apply to everyone. One example: USAGE on the SNOWFLAKE database so all users can read ACCOUNT_USAGE views. Used carelessly, it is a security hole. Granting SELECT on a sensitive table to PUBLIC opens that table to the entire account. Grants to PUBLIC are easy to add and hard to walk back once business teams start relying on what they can see.
Micro-Concept 7: UBAC, the Third Model
Current Snowflake docs name a third model alongside DAC and RBAC: UBAC, User-Based Access Control. UBAC lets you grant a privilege directly to a user, bypassing roles entirely. It only takes effect when the user runs with USE SECONDARY ROLES = ALL. That is why it is narrow by design, aimed at private development, personal Streamlit apps, and similar single-user scenarios.
For C03 as it currently stands, the standard answer to “which access-control models does Snowflake use?” is DAC + RBAC. That is the exact pair in the official practice bank (Q18). Recognize UBAC if it shows up as a distractor or an “all that apply” option. Pick the DAC + RBAC pair when the question asks for Snowflake’s primary access-control framework.
Cheat Sheet
| Concept | What to remember |
|---|---|
| RBAC one-liner | Privileges → Roles → Users. Two-step grant flow. |
| DAC one-liner | Each object has an owner role with OWNERSHIP; the owner grants access on it. |
| Snowflake uses… | Both DAC and RBAC together. UBAC exists for narrow cases. |
| Sample Q4 answer | Owner-grants-access framework = Discretionary Access Control (DAC). |
| Who owns objects? | Roles, not users. The role active during CREATE becomes the owner. |
| OWNERSHIP transfer | GRANT OWNERSHIP ON <obj> TO ROLE <other_role>; |
| Read-a-table privileges | Database USAGE + Schema USAGE + Table SELECT. Outermost → innermost. |
| Future grants | GRANT ... ON FUTURE TABLES IN SCHEMA ... applies to objects created later. |
| Role inheritance | Grant role to role; parent inherits child’s privileges. Broadest role sits on top. |
| Account roles | Account-wide; USE ROLE-activatable; CREATE ROLE. |
| Database roles | Scoped to one database; NOT USE ROLE-activatable; granted to account roles; CREATE DATABASE ROLE. |
| PUBLIC role | Every user has it; every role inherits it; cannot be dropped. Grants to PUBLIC = everyone. |
| UBAC | Privileges granted directly to a user. Active only with USE SECONDARY ROLES = ALL. |
Four near-miss traps to recognize on the exam:
(1) “In RBAC, privileges are granted directly to users.” False. RBAC’s defining property is the two-step Privilege → Role → User flow. Direct-to-user grants are UBAC, which only activate with USE SECONDARY ROLES = ALL. (2) “Users own the objects they create.” False. Roles own objects. The role active during CREATE is the owner. This trap catches engineers coming from Postgres or Oracle, where the owner is a user. (3) “Database roles can be activated in a user session with USE ROLE.” False. Database roles only work through an account role they have been granted to. USE ROLE my_db.my_db_role errors out. (4) “Snowflake only uses RBAC.” False. DAC and RBAC coexist, and UBAC is a narrower third option. When Sample Q4 phrasing appears (owner grants access on objects they own), answer DAC. When the stem says “privileges are organized via roles,” answer RBAC. When it asks about Snowflake’s access control framework without further qualification, the safe answer is DAC and RBAC together.
Hands-On Lab
Create two functional roles and plumb them into the SYSADMIN hierarchy.
USE ROLE ACCOUNTADMIN;
USE WAREHOUSE lab_xs;
-- Create two custom account roles
CREATE ROLE IF NOT EXISTS analyst_r;
CREATE ROLE IF NOT EXISTS engineer_r;
-- Grant them into SYSADMIN so they sit under SYSADMIN's hierarchy.
-- Snowflake best practice: every custom role rolls up to SYSADMIN.
GRANT ROLE analyst_r TO ROLE SYSADMIN;
GRANT ROLE engineer_r TO ROLE SYSADMIN;
-- Grant warehouse usage so the roles can actually query
GRANT USAGE ON WAREHOUSE lab_xs TO ROLE analyst_r;
GRANT USAGE ON WAREHOUSE lab_xs TO ROLE engineer_r;
-- Grant the roles to yourself so you can switch between them
GRANT ROLE analyst_r TO USER IDENTIFIER(CURRENT_USER());
GRANT ROLE engineer_r TO USER IDENTIFIER(CURRENT_USER());
Demonstrate DAC ownership. Create a database under one role and check who Snowflake records as the owner.
USE ROLE engineer_r;
CREATE DATABASE rbac_lab;
-- Confirm engineer_r owns the database (NOT the user)
SHOW DATABASES LIKE 'rbac_lab';
-- Look at the "owner" column — it shows ENGINEER_R, the role.
USE ROLE analyst_r;
-- analyst_r doesn't own rbac_lab and has no privileges on it
SHOW DATABASES LIKE 'rbac_lab';
-- Returns 0 rows — analyst_r can't even see it.
owner column on SHOW DATABASES shows a role name, not a user name. That is DAC’s defining property in one screen. The fact that analyst_r sees zero rows is default-deny in action: no privilege, no visibility.The owner role grants access to a second role without involving ACCOUNTADMIN.
-- engineer_r is the owner — it can grant without needing ACCOUNTADMIN
USE ROLE engineer_r;
GRANT USAGE ON DATABASE rbac_lab TO ROLE analyst_r;
CREATE SCHEMA rbac_lab.test_schema;
GRANT USAGE ON SCHEMA rbac_lab.test_schema TO ROLE analyst_r;
CREATE TABLE rbac_lab.test_schema.sample_data (id INT, label STRING);
INSERT INTO rbac_lab.test_schema.sample_data VALUES (1, 'alpha'), (2, 'beta');
GRANT SELECT ON TABLE rbac_lab.test_schema.sample_data TO ROLE analyst_r;
-- Now analyst_r has the full three-level read chain
USE ROLE analyst_r;
SELECT * FROM rbac_lab.test_schema.sample_data;
-- Returns the two rows. The three privileges (DB USAGE, SCHEMA USAGE, TABLE SELECT) combine.
engineer_r, not ACCOUNTADMIN, granted access on objects it owns. That is DAC’s “discretionary” in action: the owner role decides on its own. The three-tier privilege chain from Micro-Concept 4 is what makes the SELECT work. Remove any one of the three grants and the query fails.Prove default-deny: analyst_r can read, but cannot create.
USE ROLE analyst_r;
-- Should fail: analyst_r has USAGE on the database but no CREATE SCHEMA privilege
CREATE SCHEMA rbac_lab.analyst_attempt;
-- ERROR: Insufficient privileges to operate on database 'rbac_lab'
analyst_r to create schemas, engineer_r would need to GRANT CREATE SCHEMA ON DATABASE rbac_lab TO ROLE analyst_r. Nothing is implicit. Every privilege is named and granted, or it does not exist.Create a database role and watch the activation restriction in action.
USE ROLE engineer_r;
-- A database role lives INSIDE rbac_lab
CREATE DATABASE ROLE rbac_lab.read_role;
GRANT USAGE ON SCHEMA rbac_lab.test_schema TO DATABASE ROLE rbac_lab.read_role;
GRANT SELECT ON TABLE rbac_lab.test_schema.sample_data TO DATABASE ROLE rbac_lab.read_role;
-- Try to activate it directly — this should FAIL
USE ROLE rbac_lab.read_role;
-- ERROR: Database roles cannot be activated as a primary role.
-- Correct pattern: grant the database role to an ACCOUNT role
GRANT DATABASE ROLE rbac_lab.read_role TO ROLE analyst_r;
-- Now anyone using analyst_r inherits read_role's privileges
USE ROLE analyst_r;
SELECT COUNT(*) FROM rbac_lab.test_schema.sample_data;
-- Works — via the inherited database role.
USE ROLE rbac_lab.read_role failure is the whole point. A multiple-choice option that lets you activate a database role directly is wrong every time. Run this step yourself. The error message is the one you want to recognize on exam day.Transfer OWNERSHIP and watch the role in the metadata change.
USE ROLE engineer_r;
-- Transfer ownership of the sample_data table to analyst_r
GRANT OWNERSHIP ON TABLE rbac_lab.test_schema.sample_data TO ROLE analyst_r;
-- engineer_r no longer owns the table — it can't even modify it now
SHOW TABLES LIKE 'sample_data' IN rbac_lab.test_schema;
-- Check the "owner" column — now ANALYST_R.
COPY CURRENT GRANTS so dependent grants do not vanish, but for the lab a plain transfer is enough to make the point.Cleanup. Database roles drop with their parent database; account roles need explicit DROP.
USE ROLE ACCOUNTADMIN;
DROP DATABASE IF EXISTS rbac_lab; -- drops the database role too
DROP ROLE IF EXISTS analyst_r;
DROP ROLE IF EXISTS engineer_r;
-- lab_xs warehouse stays; day10_orders stays.
Snowflake Documentation
Official docs for today’s topics.
External References
Database roles and ownership transfer.
Practice Questions
Options:
A. Discretionary Access Control (DAC)
B. Identity Access Management (IAM)
C. Mandatory Access Control (MAC)
D. Role-Based Access Control (RBAC)
E. Security Assertion Markup Language (SAML)
Why A and D: Snowflake’s access control framework is built on two models named in the official docs. Discretionary Access Control (DAC) gives each object an owner role that can grant access on it. Role-Based Access Control (RBAC) grants privileges to roles, and roles to users. UBAC is now also recognized as a third model. The official C03 practice answer key still uses the DAC + RBAC pair, so that is the safe two-option pick.
Why not B: IAM is AWS terminology for cloud identity management. It is not a generic access-control model and is not Snowflake’s term.
Why not C: MAC (Mandatory Access Control) uses centrally enforced labels and policies, typical in government and defence systems. Snowflake does not implement MAC.
Why not E: SAML is a federated authentication protocol: how a user proves who they are. Access control is what happens after that. Different layer of the security stack entirely.
Options:
A. Role-Based Access Control (RBAC)
B. Discretionary Access Control (DAC)
C. Mandatory Access Control (MAC)
D. Attribute-Based Access Control (ABAC)
Why B: DAC is defined by exactly this property: each object has an owner that can discretionarily grant access on it. This is the official C03 sample exam Q4 answer almost verbatim. Anchor on the cue word “owner” in the stem.
Why not A: RBAC is also part of Snowflake’s access control, but RBAC describes how privileges flow through roles to users. It does not specifically describe owners granting access on objects they own. Mixing the two is the most common exam trap on this question type.
Why not C: MAC uses centrally enforced labels and policies. There is no owner-discretion concept in MAC, so it cannot fit the stem.
Why not D: ABAC bases decisions on attributes such as department, time of day, or IP address. Snowflake does not implement ABAC as a primary model.
Options:
A. A database role can be activated in a user session with USE ROLE, just like an account role.
B. A database role is scoped to a single database; it cannot be activated directly and must be granted to an account role to be used.
C. Database roles are stored in the SNOWFLAKE database and apply to every account in the organization.
D. Database roles replace account roles entirely; account roles are deprecated.
Why B: Database roles are scoped to one database and their privileges only apply inside that database. They cannot be activated as a primary role. The only way to use one is to grant it to an account role and activate the account role. Re-read Micro-Concept 5 if missed.
Why not A: The most-tested trap on database roles. USE ROLE my_db.my_db_role errors out by design. If an option offers this, it is wrong every time.
Why not C: Database roles live inside their owning database, not in the SNOWFLAKE shared database. They do not span accounts.
Why not D: Account roles are not deprecated. Both role types coexist, and account roles remain the foundation of the system role hierarchy.
Options:
A. Whoever ran the CREATE statement (Alice or Bob) is the personal owner of the table.
B. The role analyst_r owns the table; both Alice and Bob can manage it because both have analyst_r.
C. ACCOUNTADMIN automatically owns all newly created tables.
D. PUBLIC owns the table by default.
Why B: Snowflake’s DAC implementation puts ownership on the role, not on the user. The role active during CREATE becomes the owner, and anyone holding that role can exercise the OWNERSHIP privilege: granting, modifying, dropping. Re-read Micro-Concept 3 if missed.
Why not A: The “user owns what they create” assumption carries over from Postgres and Oracle. In Snowflake it is wrong. If Alice’s user account is dropped, the table does not become orphaned. analyst_r still owns it.
Why not C: ACCOUNTADMIN has broad authority but does not automatically own newly created objects. The role active in the creating session is the owner, full stop.
Why not D: PUBLIC is the default-floor pseudo-role for grants that should apply to everyone. It never auto-owns user-created objects.
Options:
A. 1) Schema USAGE 2) Database USAGE 3) Table USAGE
B. 1) Schema USAGE 2) Database USAGE 3) Table SELECT
C. 1) Database USAGE 2) Schema USAGE 3) Table SELECT
D. 1) Database USAGE 2) Schema USAGE 3) Table INSERT
Why C: Privileges flow through the container hierarchy from the outside in. To read a table you need USAGE on the database, then USAGE on the schema, then SELECT on the table. Missing any one of the three blocks the query. Re-read Micro-Concept 4 if missed.
Why not A: USAGE is not a valid privilege on tables. It applies to schemas, databases, warehouses, integrations, and functions. The option is constructed to catch readers who memorize “USAGE” without remembering which object types accept it.
Why not B: The privilege names are right but the order is wrong. Schema USAGE before Database USAGE is impossible. Without database USAGE, the schema is not even reachable. Order is part of the answer on this question type.
Why not D: INSERT is a real table privilege but it grants the right to write new rows, not read existing ones. The privilege required to query is SELECT. Classic distractor swap.
Today you learned: Snowflake runs two access-control models side by side. RBAC grants privileges to roles and roles to users in a strict two-step flow. Privileges are never granted directly to users. DAC gives every object an owner role with the OWNERSHIP privilege, and that owner role can grant access on the object at its own discretion. The owner is always a role, not a user. The role active during CREATE becomes the owner, and OWNERSHIP can be transferred to another role with a single grant. To query a table, a role needs USAGE on the database, USAGE on the schema, and SELECT on the table, outermost to innermost. Account roles are account-wide and activatable with USE ROLE. Database roles are scoped to one database and cannot be activated directly: they must be granted to an account role. PUBLIC is the built-in pseudo-role every user inherits. UBAC is a newer third model that grants privileges directly to a user, and it only takes effect with USE SECONDARY ROLES = ALL. For C03, the standard pair remains DAC + RBAC.
Key takeaway: Owner grants access → DAC. Privileges go through roles → RBAC. Which models does Snowflake use → both. Every Domain 2 access-control question on the exam decomposes into one of these three cues plus one privilege-hierarchy check (Database USAGE → Schema USAGE → Table SELECT).
Tomorrow (Day 19): System Roles + Custom Roles + Secondary Roles. We zoom into the role layer: the six system roles (ORGADMIN, ACCOUNTADMIN, SECURITYADMIN, USERADMIN, SYSADMIN, PUBLIC) and what each can actually do. The traps include ACCOUNTADMIN is not a superuser, SECURITYADMIN (not SYSADMIN) creates network policies, and USERADMIN is the standard role for creating users and roles. Plus the secondary-role mechanism (USE SECONDARY ROLES ALL | NONE | role1, role2) that lets one user combine privileges from multiple roles in a single session.