Learning Platform
Глоссарий Troubleshooting
Урок 12.04 · 22 мин
Средний
grantsPermissionsMulti-environmentSecurityRBAC

grants config: declarative permissions на models/seeds/snapshots

В production консумеры (BI tools, ML services, analysts) подключаются к warehouse и читают данные. Им нужны permissions: SELECT на marts, иногда USAGE на schema. Без grants — SELECT permission denied for table.

Manual grants (через GRANT SELECT ON ... TO role в SQL) — fragile: someone forgets to grant after creating new model, или revoke remained after model dropped. dbt 1.4+ предоставляет grants config — declarative permissions в YAML, applied automatically при dbt run.

Этот урок — про syntax grants, cumulative behavior (+grants), per-environment grants, и production patterns.

RBAC: governance-framework для dbt grants

Basic grants syntax

В model YAML:

- name: customer_metrics
  config:
    materialized: table
    grants:
      select: ['analyst_role', 'bi_tool_role']

После dbt run:

GRANT SELECT ON customer_metrics TO analyst_role;
GRANT SELECT ON customer_metrics TO bi_tool_role;

Permissions applied automatically. Idempotent — running again не fails.


Grants on dbt_project.yml level

Per-folder:

# dbt_project.yml
models:
  my_project:
    marts:
      +grants:
        select: ['analyst_role']

    marts:
      finance:
        +grants:
          select: ['finance_analyst_role', 'cfo_role']

All marts models: analyst_role can SELECT. Marts/finance models: ALSO finance_analyst_role and cfo_role.

This is cumulative when using +grants — applies grants from parent + this level.


Cumulative +grants vs override grants:

Cumulative (default with +):

models:
  my_project:
    +grants:
      select: ['all_analyst']

    marts:
      +grants:
        select: ['marts_analyst']    # adds to inherited

    marts:
      finance:
        +grants:
          select: ['finance_role']    # adds further

customer_metrics in marts/finance gets grants: all_analyst + marts_analyst + finance_role.

Override (without + или in config block):

- name: customer_metrics
  config:
    grants:
      select: ['only_this_role']   # overrides any inherited

customer_metrics gets only only_this_role. Inherited grants ignored.

When to use:

  • +grants: build up permissions layer by layer (project default + per-folder additions)
  • grants: in config: override for specific model (e.g., one model needs different permissions)

Multiple privilege types

- name: customer_metrics
  config:
    grants:
      select: ['analyst_role']      # SELECT only
      insert: ['etl_role']          # also INSERT
      update: ['etl_role']          # also UPDATE

Most common: select. Others (insert, update, delete, references) — rare для dbt-managed models (dbt owns the table).

Sometimes need them для shared tables (e.g., dim_dates table updated by ETL outside dbt).


Per-environment grants

# dbt_project.yml
models:
  my_project:
    marts:
      +grants:
        select: "{{ ['analyst_role'] if target.name == 'prod' else [] }}"

In prod: analyst_role can SELECT. In dev / ci: no grants (no external consumers).

This avoids granting analyst_role on developer’s personal schemas (dbt_alice_dev_marts).

Полный пример:

models:
  my_project:
    +grants:
      select: "{{ ['public_role'] if target.name == 'prod' else [] }}"

    marts:
      +grants:
        select: "{{ ['analyst_role'] if target.name == 'prod' else [] }}"

    marts:
      finance:
        +grants:
          select: "{{ ['finance_role', 'cfo_role'] if target.name == 'prod' else [] }}"

Per-target, per-folder, cumulative.


Warehouse support

Warehousegrants support
SnowflakeFull (GRANT SELECT, INSERT, etc.)
BigQueryThrough IAM roles, slightly different
PostgresFull SQL GRANT
DuckDBLimited — Local DuckDB не имеет roles. Через extensions / external auth.
RedshiftFull SQL GRANT

For DuckDB course — grants documented но не actively applied. Focus на pattern, не implementation.


Common patterns

Pattern 1: Public marts, private staging

models:
  my_project:
    staging:
      # no grants — internal only

    intermediate:
      # no grants — internal only

    marts:
      +grants:
        select: ['analyst_role', 'bi_tool_role']

Consumer-facing marts: public. Internal staging: private.

Pattern 2: Granular permissions per business unit

models:
  my_project:
    marts:
      finance:
        +grants:
          select: ['finance_analyst', 'cfo', 'controller']

      marketing:
        +grants:
          select: ['marketing_analyst', 'cmo']

      product:
        +grants:
          select: ['product_analyst', 'cpo']

Each business unit sees only their data.

Pattern 3: PII protection

- name: customer_pii
  config:
    grants:
      select: ['pii_authorized_role']   # restricted

- name: customer_metrics_anonymized
  config:
    grants:
      select: ['analyst_role', 'bi_tool_role']   # public

PII separately. Analysts get anonymized version. PII authorized role gets full data.

Pattern 4: Tiered access

models:
  my_project:
    marts:
      +grants:
        select: ['analyst_role']

      executive:
        +grants:
          select: ['executive_role']   # additional, executive level

      sensitive:
        +grants:
          select: ['compliance_role']   # only compliance team

Different tiers, cumulative gains.


Полный пример

# dbt_project.yml
name: my_project

models:
  my_project:
    +materialized: table

    staging:
      +schema: staging
      # no grants — internal

    intermediate:
      +schema: intermediate
      # no grants

    marts:
      +schema: marts
      +grants:
        select: "{{ ['analyst_role'] if target.name == 'prod' else [] }}"

      finance:
        +schema: marts_finance
        +grants:
          select: "{{ ['finance_role'] if target.name == 'prod' else [] }}"

      marketing:
        +schema: marts_marketing
        +grants:
          select: "{{ ['marketing_role'] if target.name == 'prod' else [] }}"

Result в prod:

  • staging schemas: no grants
  • intermediate: no grants
  • marts (default): analyst_role SELECT
  • marts/finance: analyst_role + finance_role SELECT
  • marts/marketing: analyst_role + marketing_role SELECT

In dev: no grants anywhere.


What +grants does behind scenes

При dbt run для модели:

  1. dbt materializes table
  2. dbt applies grants — runs GRANT SELECT ON {table} TO {role} для каждого role
  3. dbt ALSO revokes grants not in current config (cumulative revoke)

Last step — cleanup: if previously granted role X is removed from config, dbt revokes на next run.

Result: YAML is source of truth for permissions. Out-of-band manual grants get overwritten.

Это feature, не bug — consistent permissions, no drift.

Trade-off: if you have non-dbt-managed grants on same table — they get wiped. Solution — manage all grants через dbt config.


Schema-level vs table-level grants

Grants in dbt are table-level. Some warehouses also support schema-level:

-- Schema-level (Snowflake)
GRANT USAGE ON SCHEMA marts TO analyst_role;
GRANT SELECT ON ALL TABLES IN SCHEMA marts TO analyst_role;
GRANT SELECT ON FUTURE TABLES IN SCHEMA marts TO analyst_role;

dbt doesn’t directly manage schema-level grants. Patterns:

1. On-run-start hook:

on-run-start:
  - "{% if target.name == 'prod' %} GRANT USAGE ON SCHEMA marts TO analyst_role; {% endif %}"

Applies once при каждом dbt run. Idempotent.

2. Future grants (Snowflake):

-- One-time setup
GRANT SELECT ON FUTURE TABLES IN SCHEMA marts TO analyst_role;

All new tables automatically granted. dbt creates new table -> grant applies. But dbt grants config could still revoke!

Solution: don’t use FUTURE grants with dbt grants config — choose one mechanism.


Anti-patterns

1. Hardcoded grants in environment-specific way

+grants:
  select: ['alice_role', 'bob_role']   # specific developers!

Brittle — if Alice leaves, manual cleanup needed. Use group roles (analyst_role includes Alice and Bob).

2. Grants in dev

+grants:
  select: ['analyst_role']

Without target.name guard — applies в dev. analyst_role grants on developer’s personal schemas. Confusing. Use {{ [...] if target.name == 'prod' else [] }}.

3. Too many roles, hard to audit

+grants:
  select: ['role_a', 'role_b', 'role_c', 'role_d', 'role_e', 'role_f']

Hard to audit. Use role hierarchies (Snowflake): analyst_role includes finance + marketing + product roles. One role at table level, role membership manages who.

4. Grants only некоторых моделей в marts

Inconsistent. Bot stunned: ‘Why does this mart have grants but next one doesn’t?’. Use +grants at marts level — all inherit.

5. Forgot grants on new model

Without +grants inheritance — new mart needs explicit grants. Solution — set defaults at folder level (marts/+grants), new models inherit automatically.

6. Stale grants после rename

If model fct_orders renamed to fct_order_lines, old grants on fct_orders (now dropped) — gone. New grants on fct_order_lines need re-apply. dbt handles это automatically at run.

7. Manual SQL grants conflict with dbt

-- Manually run
GRANT SELECT ON marts.fct_orders TO new_role;

Next dbt run — dbt overwrites grants from config. new_role лost.

Solution: add new_role to YAML config. Persist via dbt.


CI gate для grants

# scripts/check_grants.py
import json

with open('target/manifest.json') as f:
    manifest = json.load(f)

errors = []
for node in manifest['nodes'].values():
    if node['resource_type'] != 'model':
        continue

    is_mart = any(p == 'marts' for p in node['fqn'])
    if not is_mart:
        continue

    grants = node.get('config', {}).get('grants', {})
    if not grants.get('select'):
        errors.append(f'Mart {node[\"name\"]} has no SELECT grants — consumers will fail')

if errors:
    print('\n'.join(errors))
    exit(1)

Catches missing grants на consumer-facing models.


Попробуй сам

  1. Add grants to one mart model:

    - name: customer_metrics
      config:
        grants:
          select: ['analyst_role']
  2. Run on Postgres/Snowflake:

    dbt run --select customer_metrics
  3. Verify:

    -- Snowflake
    SHOW GRANTS ON TABLE customer_metrics;
  4. Add per-target conditional:

    grants:
      select: "`{{ ['analyst_role'] if target.name == 'prod' else [] }}`"
  5. Test cumulative:

    # dbt_project.yml
    models:
      my_project:
        +grants:
          select: ['public']
        marts:
          +grants:
            select: ['analyst_role']
  6. Remove role from config — verify dbt revokes на next run.


Ключевые выводы

  1. grants config (1.4+) — declarative permissions в YAML, applied automatically при dbt run. select, insert, update privilege types.
  2. Cumulative +grants: combines roles from project / folder / model level. Total permissions = union.
  3. Override grants: in model config — replaces inherited grants. Useful для exceptions.
  4. Per-environment через target.name: {{ ['role'] if target.name == 'prod' else [] }}. Avoid grants в dev на personal schemas.
  5. Warehouse support: Snowflake full, Postgres full, BigQuery (через IAM, slightly different), Redshift full, DuckDB limited.
  6. Patterns: public marts / private staging, granular per-business-unit, PII protection, tiered access.
  7. dbt revokes out-of-band grants — YAML is source of truth. Manage all grants via dbt.
  8. Anti-patterns: hardcoded developer names, grants в dev без guard, too many roles (use hierarchies), inconsistent грантеs across marts, manual SQL grants (conflicts).
Проверка знанийKnowledge check
Команда добавила `grants: select: ['analyst_role']` в dbt_project.yml на marts. После dbt run в dev — analyst_role получает SELECT на dbt_alice_dev_marts.customer_metrics. Что нежелательно?
ОтветAnswer
Это **leak permissions to dev schemas**. analyst_role не должен видеть Alice's personal/test data.\n\n**Problems**:\n\n1. **Privacy**: Alice testing с PII data в local dev -> analyst_role sees it\n2. **Confusion**: analyst sees `dbt_alice_dev_marts.customer_metrics` and Alice's incomplete data\n3. **Catalog pollution**: Tableau / Looker sees all dev schemas — clutter\n4. **Unnecessary**: dev data is throwaway, no consumer needs access\n\n**Fix** — gate grants by target:\n\n```yaml\n# dbt_project.yml\nmodels:\n my_project:\n marts:\n +grants:\n select: "{{ ['analyst_role'] if target.name == 'prod' else [] }}"\n```\n\nResult:\n- **Production** (target.name=prod): grants applied. analyst_role gets SELECT на marts.customer_metrics\n- **Dev** (target.name=dev): empty list `[]` — no grants. Alice's dev schemas private\n- **CI** (target.name=ci): also empty — CI doesn't need analyst access\n\n**More refined** — conditional based on env:\n\n```yaml\n+grants:\n select: >\n {{\n ['analyst_role', 'bi_role'] if target.name == 'prod'\n else ['ci_test_role'] if target.name == 'ci'\n else []\n }}\n```\n\nCI gets ci_test_role (для testing tableau-like queries). Prod gets full analyst access. Dev gets nothing.\n\n**Multi-folder с per-target**:\n\n```yaml\nmodels:\n my_project:\n marts:\n +grants:\n select: "{{ ['analyst_role'] if target.name == 'prod' else [] }}"\n finance:\n +grants:\n select: "{{ ['finance_role'] if target.name == 'prod' else [] }}"\n marketing:\n +grants:\n select: "{{ ['marketing_role'] if target.name == 'prod' else [] }}"\n```\n\nCumulative gains в production. Empty в dev.\n\n**Verification после fix**:\n\n```bash\n# Dev — verify no grants\ndbt run --target dev --select customer_metrics\n# Then in warehouse:\nSHOW GRANTS ON TABLE dbt_alice_dev_marts.customer_metrics;\n# Result: no grants beyond Alice's own access\n\n# Prod — verify grants applied\ndbt run --target prod --select customer_metrics\nSHOW GRANTS ON TABLE marts.customer_metrics;\n# Result: analyst_role has SELECT\n```\n\n**Production rule**: **always gate grants by target**. Never apply grants без environment check unless explicit reason. Default — production-only grants.\n\n**Alternative**: separate dbt_project.yml для different environments — overkill для most projects. Inline conditional cleaner.
Проверка знанийKnowledge check
Senior говорит: 'analyst_role нужен SELECT на 50 marts. Лучше использовать future grants Snowflake вместо dbt grants config?'. Какие проблемы?
ОтветAnswer
**Future grants** (Snowflake) — automatic grants on new objects in schema. Powerful, but conflicts с dbt grants config.\n\n**Future grants syntax** (one-time setup):\n```sql\nGRANT SELECT ON FUTURE TABLES IN SCHEMA marts TO analyst_role;\nGRANT SELECT ON FUTURE VIEWS IN SCHEMA marts TO analyst_role;\n```\n\nAll new tables/views automatically granted на creation. No per-table grants needed. Manage at schema level.\n\n**Conflict с dbt grants config**:\n\n**Scenario**:\n\n1. Setup future grants: `GRANT SELECT ON FUTURE TABLES IN SCHEMA marts TO analyst_role`\n2. dbt config:\n ```yaml\n marts:\n +grants:\n select: ['analyst_role']\n ```\n\n**При dbt run**:\n- dbt creates table `customer_metrics` в marts\n- Future grants apply: GRANT SELECT TO analyst_role (automatic)\n- dbt grants apply: GRANT SELECT TO analyst_role (config-driven)\n- **No conflict** — both grant same access\n\n**Conflict scenario**:\n\nLater, change config:\n```yaml\nmarts:\n +grants:\n select: ['analyst_role', 'new_role']\n```\n\nAt next dbt run:\n- dbt revokes grants NOT in config\n- Hmm — but future grants applied analyst_role outside dbt's awareness\n- **dbt might revoke future-grant-applied analyst_role!**\n\n**Or worse** — remove role from config:\n```yaml\nmarts:\n +grants:\n select: ['new_role'] # removed analyst_role\n```\n\n- dbt revokes analyst_role's grants on customer_metrics\n- Future grant **re-applies** на next run (table re-creation)\n- **Continuous oscillation** — broken state\n\n**Other future grants issues**:\n\n**1. Permissions drift**:\nSomeone manually grants new_role outside dbt. Future grants don't reapply. dbt config doesn't know. Drift.\n\n**2. Difficult to audit**:\nIs role granted because of future grant? Or dbt config? Or manual? Multiple sources of truth.\n\n**3. Granularity**:\nFuture grants are **schema-wide**. dbt grants are **per-table**. Can't have different roles на different models с future grants.\n\n**Recommended approach** — **one mechanism only**:\n\n**Option A — dbt grants config (recommended)**:\n- All grants в YAML\n- Per-table granularity\n- Source of truth: dbt project\n- Trade-off: more YAML, но manageable\n\n**Option B — Future grants (alternative)**:\n- Schema-wide grants\n- One-time setup, no maintenance\n- Trade-off: no granularity, no per-table differentiation\n- **Disable dbt grants config** if using future grants\n\n**Don't mix**. Choose one.\n\n**Production-grade choice — Option A**:\n\n```yaml\n# dbt_project.yml\nmodels:\n my_project:\n marts:\n +grants:\n select: "{{ ['analyst_role'] if target.name == 'prod' else [] }}"\n finance:\n +grants:\n select: "{{ ['finance_role'] if target.name == 'prod' else [] }}"\n sensitive:\n +grants:\n select: "{{ ['compliance_role'] if target.name == 'prod' else [] }}"\n```\n\n50 marts × inheritance from `marts` level = analyst_role gets 50 grants. Per-folder additions для granular. All в version control.\n\n**Audit trail**: SHOW GRANTS reflects YAML. Roles can be confirmed by grep на YAML. CI tests can verify expected grants.\n\n**Cost**: more YAML. Small price для consistency.\n\n**When future grants OK**:\n- Small projects (5-10 marts)\n- No granular permissions needed\n- Stable role structure\n- Не using dbt grants config\n\n**Production large project**: dbt grants config wins. Per-table granularity, version-controlled, auditable.

Проверьте понимание

Результат: 0 из 0
Прикладной
Вопрос 1 из 5. Команда добавила `grants: select: ['analyst_role']` в marts. После dbt run в dev — analyst_role gets SELECT на dbt_alice_dev_marts.*. Что не так?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 5