Warehouse privileges¶
dbt charts Cloud
This page describes dbt charts Cloud, the hosted
product — these features are not part of the open source dct
engine.
dbt charts only ever reads from your warehouse. It runs SELECT queries to draw
boards and never writes to your tables. The credential you give a
connection should be able to do exactly that much and no more.
One exception is worth knowing before you scope the role: a board may declare
setup_sql, which is allowed to create temporary functions, tables, and views
for the length of its own query session. Nothing it creates outlives the session
or touches your data. If your boards use it, the role needs whatever temporary-
object privilege your warehouse requires — on PostgreSQL that is TEMPORARY on
the database.
This page covers creating a read-only role and granting it access to the tables your boards read.
Don't reuse your dbt credential¶
The most convenient credential is usually the one already in your project's
profiles.yml, and it is the wrong one. That credential builds your models — it
holds CREATE, and usually DROP, on the schemas dbt writes to. Handing it to
a reporting tool gives that tool the ability to destroy the warehouse it is
supposed to be reading.
Cloud pre-fills a new connection's host, database, and other non-secret settings
from your committed profiles.yml, because that saves you looking them up.
Secrets are never read from your repository. Enter a separate read-only
credential in the password field.
1. Create the role¶
dbt does not create roles or users, so this part is a one-time piece of SQL you run yourself. Substitute your own names, schemas, and a strong password.
PostgreSQL¶
CREATE ROLE dbt_charts_ro LOGIN PASSWORD 'replace-me';
GRANT CONNECT ON DATABASE analytics TO dbt_charts_ro;
GRANT USAGE ON SCHEMA analytics TO dbt_charts_ro;
Redshift¶
Snowflake¶
CREATE ROLE dbt_charts_ro;
CREATE USER dbt_charts_svc PASSWORD = 'replace-me' DEFAULT_ROLE = dbt_charts_ro;
GRANT ROLE dbt_charts_ro TO USER dbt_charts_svc;
GRANT USAGE ON WAREHOUSE reporting_wh TO ROLE dbt_charts_ro;
GRANT USAGE ON DATABASE analytics TO ROLE dbt_charts_ro;
GRANT USAGE ON SCHEMA analytics.marts TO ROLE dbt_charts_ro;
A Snowflake connection needs USAGE on a warehouse as well as on the data —
without it the role cannot run a query at all.
BigQuery¶
BigQuery uses IAM rather than SQL. Create a service account, then give it two
roles: roles/bigquery.jobUser on the project so it can run queries, and
roles/bigquery.dataViewer on each dataset it should read.
gcloud iam service-accounts create dbt-charts-ro --project my-project
gcloud projects add-iam-policy-binding my-project \
--member "serviceAccount:dbt-charts-ro@my-project.iam.gserviceaccount.com" \
--role roles/bigquery.jobUser \
--condition=None
--condition=None states the binding is deliberately unconditional. Without
it, gcloud refuses to add a binding on any project whose IAM policy already
contains conditional bindings and prompts interactively instead.
Grant roles/bigquery.dataViewer per dataset rather than project-wide, so the
service account sees only what it needs.
2. Grant read access — with dbt¶
This is the recommended path for anything dbt builds. dbt has a
grants config
that is declarative, lives in git alongside the models it describes, and is
re-applied on every run. Add one block to dbt_project.yml:
models: my_project: +grants: select: - dbt_charts_ro
On your next dbt run, dbt grants SELECT on every model it builds to
dbt_charts_ro. New models are covered automatically as they are added, which is
the part that hand-written SQL cannot do.
Start at this level — one grant covering everything your project builds. It is the right amount of access to begin with, and dbt charts can help you narrow it later once you have boards to narrow against.
Snowflake and BigQuery normalize identifiers differently, so write the grantee
name the way your warehouse expects it. On BigQuery the grantee is an IAM
principal, for example serviceAccount:dbt-charts-ro@my-project.iam.gserviceaccount.com.
Narrowing to part of your project¶
The same config works at any level of the models: tree, so a folder can grant
differently from the project default:
models: my_project: +grants: select: - dbt_charts_ro finance: +grants: +select: - finance_team
Note the + on +select:. Inside a grants block, a privilege written as
select: replaces whatever was inherited, while +select: adds to it.
Written without the + above, models under finance/ would be readable by
finance_team and no longer by dbt_charts_ro.
This is easy to get wrong and fails quietly — one model out of hundreds stops
being readable. If you take one thing from this page, take the +.
3. Grant read access — by hand¶
Raw SQL is the right tool in two cases: sources dbt does not build (a landing schema loaded by something else), and one-off situations where you would rather apply the privilege yourself.
PostgreSQL and Redshift¶
GRANT SELECT ON ALL TABLES IN SCHEMA raw TO dbt_charts_ro;
ALTER DEFAULT PRIVILEGES IN SCHEMA raw GRANT SELECT ON TABLES TO dbt_charts_ro;
ALTER DEFAULT PRIVILEGES only covers objects created by the role that runs it,
so run it as the role that loads that schema — otherwise new tables will not be
covered.
Snowflake¶
GRANT SELECT ON ALL TABLES IN SCHEMA analytics.raw TO ROLE dbt_charts_ro;
GRANT SELECT ON FUTURE TABLES IN SCHEMA analytics.raw TO ROLE dbt_charts_ro;
GRANT SELECT ON ALL VIEWS IN SCHEMA analytics.raw TO ROLE dbt_charts_ro;
GRANT SELECT ON FUTURE VIEWS IN SCHEMA analytics.raw TO ROLE dbt_charts_ro;
ALL TABLES does not include views, and FUTURE grants are what cover objects
created later — all four statements matter.
Hand-written grants and dbt do not mix¶
If you grant by hand on a relation dbt builds, expect to lose it. There are two separate ways that happens, and both are worth knowing.
Once a model has any grants: config, dbt owns its whole grant set. On the
next run dbt lists the relation's current grants, compares them to what your
config declares, and revokes anything it finds that you did not configure. A
privilege you granted by hand is not merely forgotten — it is explicitly
revoked. On Redshift and BigQuery this is the behaviour you will hit.
On PostgreSQL and Snowflake, a full refresh drops grants outright. Both
treat a replaced relation as a new object that does not inherit the old one's
privileges, so dbt run --full-refresh loses hand-issued grants regardless of
config. (Snowflake can be told otherwise per model with
copy_grants,
which defaults to off.)
There is one case where a hand-issued grant does survive: a project with no
grants: config anywhere. dbt skips grant handling entirely, so nothing is
revoked — until the relation is next replaced, on the warehouses above.
The practical rule is simple. If dbt builds it, grant it in +grants:. If dbt
does not build it, grant it by hand.
Verifying it worked¶
A connection test tells you the credential can reach the warehouse and log in.
Being able to log in is not the same as being able to read your tables — a role
with no SELECT anywhere still authenticates successfully.
After granting, run dbt run so the grants are applied, then open a board. If a
query fails on permissions, the message names the relation the role cannot read,
which is the one to add.
Related¶
- Data connections — creating and testing a connection
- Access control — which people can see which boards, a separate concern from what the warehouse credential can read