Least-privilege Snowflake grants
Provision the read-only role the dashboard connects with.
Provisioning script
Run as ACCOUNTADMIN once, then store the private key as a secret.
-- 1. Dedicated, least-privilege reporting role
use role accountadmin;
create role if not exists cost_dashboard_reader;
-- 2. Read-only access to account usage (no data access to user tables)
grant imported privileges on database snowflake to role cost_dashboard_reader;
grant database role snowflake.usage_viewer to role cost_dashboard_reader;
-- 3. Small dedicated warehouse with aggressive auto-suspend
create warehouse if not exists cost_dashboard_wh
warehouse_size = xsmall
auto_suspend = 60
auto_resume = true
initially_suspended = true;
grant usage, operate on warehouse cost_dashboard_wh to role cost_dashboard_reader;
-- 4. Service user with key-pair authentication (no password, no UI login)
create user if not exists svc_cost_dashboard
default_role = cost_dashboard_reader
default_warehouse = cost_dashboard_wh
type = service
rsa_public_key = '<PASTE_PUBLIC_KEY_BODY>';
grant role cost_dashboard_reader to user svc_cost_dashboard;
-- 5. Optional: pre-aggregated reporting table to cut ACCOUNT_USAGE scan cost
create schema if not exists analytics.finops;
create table if not exists analytics.finops.cost_analytics (
usage_date date,
warehouse_name string,
database_name string,
credits_used number(38,6),
compute_credits number(38,6),
cloud_services_credits number(38,6),
storage_tb number(38,6)
);
grant usage on database analytics to role cost_dashboard_reader;
grant usage on schema analytics.finops to role cost_dashboard_reader;
grant select on table analytics.finops.cost_analytics to role cost_dashboard_reader;
-- 6. Never grant: ACCOUNTADMIN, SECURITYADMIN, SYSADMIN, or write privileges.
Operational notes
Security and cost-control behaviour of the data layer.
- Key-pair (unencrypted PKCS#8) or OAuth only — password auth is rejected by the app.
- The service user holds no write privileges anywhere in the account.
- ACCOUNT_USAGE views carry up to 45 minutes of latency; reports are cached for 5 minutes.
- Query ranges are clamped to 400 days and query listings are paginated server-side.
- SQL text from QUERY_HISTORY is returned only for the current page and hidden in the UI by default.