@@ -762,6 +762,66 @@ CREATE OR REPLACE MASKING POLICY "{{ database }}".{{ schema }}.{{ policy }}_{{ d
Only one policy can be applied to a column so users that need access will have to have the permissions granted using the applied masking role.
##### Row Level Security
Row Level Security controls which rows a user can see in a table or view. Rather than masking individual column values, a Snowflake [Row Access Policy](https://docs.snowflake.com/en/user-guide/security-row-intro) is attached to the relation and evaluated at query time — rows the current user is not entitled to see are filtered out entirely.
The macros that manage this live in `macros/dbt_snowflake_row_access/` and follow the same post-hook pattern as [Dynamic Masking](#dynamic-masking).
To enable row-level security on a model, add `rls_policy` to the model's `meta` config in `schema.yml`. There are two modes:
**Simple (role-based):** The `rls_policy` value must match a valid Snowflake role name. Access is granted to any user whose session holds a role matching that name.
```yaml
-name:my_model
config:
meta:
rls_policy:"my_snowflake_role_name"
```
**Entitlement-based:** The `rls_policy` value is simply the name that will be given to the Snowflake Row Access Policy object when it is created — it does not need to match any Snowflake role. Access is determined row-by-row by joining an entitlement model at query time. The entitlement model must be a dbt model in the project with a column identifying the Snowflake username (defaults to `snowflake_user_name`) and a join column matching the filter column on the secured table. It is recommended to reference the entitlement model being used in the model's description for discoverability.
```yaml
-name:my_model
config:
meta:
rls_policy:"my_policy_name"
rls_config:
entitlement_model:"ent_my_entitlement_model"
entitlement_column:"my_join_key_column"
filter_column:"my_join_key_column"
user_identifier_column:"snowflake_user_name"
```
The `rls_config` keys are:
-`entitlement_model`: The name of the dbt model that defines which rows each user can access
-`entitlement_column`: The column in the entitlement model to join against the secured table
-`filter_column`: The column in the secured table whose value is passed to the policy for filtering
-`user_identifier_column`: The column in the entitlement model holding the Snowflake username. Defaults to `snowflake_user_name` if omitted.
A `post-hook` running the `secure_model` macro must be configured for any model that needs row-level security applied. This is typically set at the directory level in `dbt_project.yml`:
```yaml
my_schema_directory:
+post-hook:
-"{{secure_model()}}"
```
The macro is a no-op for models that have no `rls_policy` in their `config.meta`, so it is safe to apply broadly to a schema directory.
`TRANSFORMER` and `LOADER` roles always have full access regardless of the policy mode.
The macros that implement this pattern are:
| Macro | Role |
|---|---|
| `secure_model` | Post-hook entry point — reads `rls_policy` from the model's `config.meta` and delegates to `apply_row_access_policy` |
| `get_tables_to_secure` | Walks the dbt graph to collect all nodes with `rls_policy` configured, returning the fully-qualified table name, policy name, and optional `rls_config` |
| `apply_row_access_policy` | Orchestrator — queries `information_schema` for table type and filter column data type, then calls `create_row_access_policy` and `set_row_access_policy` |
| `create_row_access_policy` | Creates or alters the Snowflake Row Access Policy in simple or entitlement-based mode |
| `set_row_access_policy` | Lowest-level DDL: drops all existing row access policies from the relation then adds the specified policy |
#### Staging
Prior to our implementation of Kimball modeling, most all of our models would have fallen into Staging category.