Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
140 changes: 138 additions & 2 deletions docs-mintlify/admin/connect-to-data/data-sources/snowflake.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ GRANT SELECT ON FUTURE TABLES IN DATABASE ABC TO ROLE XYZ;

- [Account/Server URL][snowflake-docs-account-id] for Snowflake.
- User name and password or an RSA private key for the Snowflake account.
In Cube Cloud, you can authenticate with [OIDC workload
identity][ref-oidc-overview] instead — the same role grants apply to the
mapped service user.
- Optionally, the warehouse name, the user role, and the database name.

## Setup
Expand Down Expand Up @@ -63,6 +66,136 @@ The following fields are required when creating a Snowflake connection:
<img src="https://ucarecdn.com/2a113d20-33ca-4634-b6fa-8886df4d215c/" alt="Cube Cloud Snowflake Configuration Screen" />
</Frame>

#### OIDC workload identity

Instead of a password or key pair, Cube Cloud deployments can authenticate
to Snowflake with [OIDC workload identity][ref-oidc-overview]: a Snowflake
External OAuth integration trusts Cube's OIDC issuer, and the driver
presents a short-lived Cube-minted JWT — no long-lived secrets to
provision or rotate. Snowflake validates each connection's token against
your tenant's public JWKS, maps the token's `sub` claim to a Snowflake
user, and authorizes the session role through the token's `scp` claim.

Start with the [OIDC overview][ref-oidc-overview] for the concepts (issuer,
token configs, custom claims, target env var) and to enable OIDC for your
tenant, then:

<Steps>
<Step title="Create the token config">
In **Admin → OIDC**, click **Add Config** and create a config with:

| Field | Value |
| ------------------------ | ----------------------------------------------------------------------------------------------- |
| **Audience Type** | `Custom` |
| **Name** | `snowflake` |
| **Custom Audience** | `https://<account-identifier>.snowflakecomputing.com` |
| **Subject Claim Format** | Keep the default, or pick a richer template — the rendered `sub` must match the Snowflake user's `LOGIN_NAME` below |
| **Custom Claims** | Claim `scp` with value `session:role-any` |
| **Target Env Var** | `CUBEJS_DB_SNOWFLAKE_OAUTH_TOKEN_PATH` |

<Frame>
<img src="/images/admin/deployment/oidc/snowflake-token-config-dialog.png" alt="Edit OIDC Token Config dialog in Cube Cloud for Snowflake: Custom audience type, the Snowflake account URL as the custom audience, a Target Env Var of CUBEJS_DB_SNOWFLAKE_OAUTH_TOKEN_PATH, and a custom claim scp set to session:role-any." />
</Frame>

The `scp` claim is the part that's easy to miss: Snowflake grants
session roles **exclusively** through it — a token without `scp`
authenticates but fails role authorization. `session:role-any` lets the
driver request any role granted to the mapped user; to pin the role
inside the token instead, use `session:role:<role-name>` and
`EXTERNAL_OAUTH_ANY_ROLE_MODE = 'DISABLE'` below. The **Target Env Var**
is how the driver finds the token: Cube sets that env var to the token
file path in every execution context (deployed pods, dev mode, and test
connection each keep the file in a different place), so the path is
never written by hand.
</Step>
<Step title="Create the External OAuth security integration">
In a Snowflake worksheet, as `ACCOUNTADMIN`:

```sql
CREATE SECURITY INTEGRATION CUBE_CLOUD_EXTERNAL_OAUTH
TYPE = EXTERNAL_OAUTH
ENABLED = TRUE
EXTERNAL_OAUTH_TYPE = CUSTOM
EXTERNAL_OAUTH_ISSUER = 'https://<tenant-name>.cubecloud.dev'
EXTERNAL_OAUTH_JWS_KEYS_URL = 'https://<tenant-name>.cubecloud.dev/.well-known/jwks.json'
EXTERNAL_OAUTH_AUDIENCE_LIST = ('https://<account-identifier>.snowflakecomputing.com')
EXTERNAL_OAUTH_TOKEN_USER_MAPPING_CLAIM = 'sub'
EXTERNAL_OAUTH_SNOWFLAKE_USER_MAPPING_ATTRIBUTE = 'LOGIN_NAME'
EXTERNAL_OAUTH_ANY_ROLE_MODE = 'ENABLE';
```

The issuer and audience must match the token config exactly. Snowflake
fetches the JWKS from your tenant's public endpoint, so no key material
changes hands.
</Step>
<Step title="Create the service user and role">
The integration maps the token's `sub` claim to a Snowflake user via
`LOGIN_NAME`. With the default subject claim format the rendered `sub`
is `cube:deployment:<deployment-id>` — the deployment ID is the number
in your deployment's console URL. If you chose a different template,
use the token-config dialog's live preview to see the exact rendered
value.

```sql
CREATE ROLE CUBE_ROLE;

CREATE USER CUBE_SVC
TYPE = SERVICE
LOGIN_NAME = 'cube:deployment:<deployment-id>'
DEFAULT_ROLE = CUBE_ROLE
DEFAULT_WAREHOUSE = <warehouse>;

GRANT ROLE CUBE_ROLE TO USER CUBE_SVC;
GRANT USAGE ON WAREHOUSE <warehouse> TO ROLE CUBE_ROLE;

-- Grant the role read access to the data Cube serves, e.g.:
GRANT USAGE ON DATABASE <database> TO ROLE CUBE_ROLE;
GRANT USAGE ON ALL SCHEMAS IN DATABASE <database> TO ROLE CUBE_ROLE;
GRANT SELECT ON ALL TABLES IN DATABASE <database> TO ROLE CUBE_ROLE;
```

`TYPE = SERVICE` blocks password logins for this user entirely — it
can only authenticate through the federation.
</Step>
<Step title="Configure the deployment">
Set the `OAUTH` authenticator and omit `CUBEJS_DB_USER` /
`CUBEJS_DB_PASS`:

```dotenv
CUBEJS_DB_TYPE=snowflake
CUBEJS_DB_SNOWFLAKE_ACCOUNT=<account-identifier>
CUBEJS_DB_SNOWFLAKE_WAREHOUSE=<warehouse>
CUBEJS_DB_NAME=<database>
CUBEJS_DB_SNOWFLAKE_ROLE=CUBE_ROLE
CUBEJS_DB_SNOWFLAKE_AUTHENTICATOR=OAUTH
```

`CUBEJS_DB_SNOWFLAKE_OAUTH_TOKEN_PATH` is **not** set here — Cube
populates it automatically thanks to the **Target Env Var** from the
token config. The driver re-reads the file on every new connection, so
the broker's automatic refresh is picked up without restarts.
</Step>
</Steps>

To verify, run any query against the Snowflake data source. On the
Snowflake side, a successful federation shows up in the login history with
`OAUTH_ACCESS_TOKEN` as the authentication factor:

```sql
SELECT event_timestamp, user_name, first_authentication_factor, is_success
FROM TABLE(SNOWFLAKE.INFORMATION_SCHEMA.LOGIN_HISTORY_BY_USER(USER_NAME => 'CUBE_SVC'))
ORDER BY event_timestamp DESC;
```

Common failure modes:

| Error | Cause |
| ----------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
| `Invalid OAuth access token` | Issuer or audience mismatch between the token config and the security integration, or the JWKS URL is unreachable. |
| `The role … is not listed in the Access Token or was filtered` | The `scp` custom claim is missing from the token config, or the role isn't granted to the mapped user. |
| `User … not found` / mapping errors | The rendered `sub` doesn't match the Snowflake user's `LOGIN_NAME` — compare against the dialog's live preview. |
| `File … provided by CUBEJS_DB_SNOWFLAKE_OAUTH_TOKEN_PATH does not exist` | The **Target Env Var** isn't set on the token config (or a stale hand-written path is configured), or the deployment is still starting up. |

Cube Cloud also supports connecting to data sources within private VPCs
if [single-tenant infrastructure][ref-dedicated-infra] is used. Check out the
[VPC connectivity guide][ref-cloud-conf-vpc] for details.
Expand All @@ -79,8 +212,8 @@ if [single-tenant infrastructure][ref-dedicated-infra] is used. Check out the
| [`CUBEJS_DB_SNOWFLAKE_ROLE`](/reference/configuration/environment-variables#cubejs_db_snowflake_role) | The Snowflake role to use when connecting to the database | [A valid Snowflake role][snowflake-docs-roles] in the account | ❌ |
| [`CUBEJS_DB_SNOWFLAKE_CLIENT_SESSION_KEEP_ALIVE`](/reference/configuration/environment-variables#cubejs_db_snowflake_client_session_keep_alive) | If `true`, [keep the Snowflake connection alive indefinitely][snowflake-docs-connection-options] | `true`, `false` | ❌ |
| [`CUBEJS_DB_NAME`](/reference/configuration/environment-variables#cubejs_db_name) | The name of the database to connect to | A valid database name | ✅ |
| [`CUBEJS_DB_USER`](/reference/configuration/environment-variables#cubejs_db_user) | The username used to connect to the database | A valid database username | |
| [`CUBEJS_DB_PASS`](/reference/configuration/environment-variables#cubejs_db_pass) | The password used to connect to the database | A valid database password | |
| [`CUBEJS_DB_USER`](/reference/configuration/environment-variables#cubejs_db_user) | The username used to connect to the database | A valid database username | ✅<sup>1</sup> |
| [`CUBEJS_DB_PASS`](/reference/configuration/environment-variables#cubejs_db_pass) | The password used to connect to the database | A valid database password | ✅<sup>1</sup> |
| [`CUBEJS_DB_SNOWFLAKE_AUTHENTICATOR`](/reference/configuration/environment-variables#cubejs_db_snowflake_authenticator) | The type of authenticator to use with Snowflake. Use `SNOWFLAKE` with username/password, or `SNOWFLAKE_JWT` with key pairs. Defaults to `SNOWFLAKE` | `SNOWFLAKE`, `SNOWFLAKE_JWT`, `OAUTH` | ❌ |
| [`CUBEJS_DB_SNOWFLAKE_PRIVATE_KEY`](/reference/configuration/environment-variables#cubejs_db_snowflake_private_key) | The content of the private RSA key | Content of the private RSA key (encrypted or not) | ❌ |
| [`CUBEJS_DB_SNOWFLAKE_PRIVATE_KEY_PATH`](/reference/configuration/environment-variables#cubejs_db_snowflake_private_key_path) | The path to the private RSA key | A valid path to the private RSA key | ❌ |
Expand All @@ -92,6 +225,8 @@ if [single-tenant infrastructure][ref-dedicated-infra] is used. Check out the
| [`CUBEJS_DB_MAX_POOL`](/reference/configuration/environment-variables#cubejs_db_max_pool) | The maximum number of concurrent database connections to pool. Default is `20` | A valid number | ❌ |
| [`CUBEJS_CONCURRENCY`](/reference/configuration/environment-variables#cubejs_concurrency) | The number of [concurrent queries][ref-data-source-concurrency] to the data source | A valid number | ❌ |

<sup>1</sup> Required when using password-based authentication. Not required with `SNOWFLAKE_JWT` (key pair) or with `OAUTH` — including [OIDC workload identity](#oidc-workload-identity) in Cube Cloud, where the driver reads a Cube-minted token from [`CUBEJS_DB_SNOWFLAKE_OAUTH_TOKEN_PATH`](/reference/configuration/environment-variables#cubejs_db_snowflake_oauth_token_path).

[ref-data-source-concurrency]: /admin/connect-to-data/concurrency#data-source-concurrency
## Pre-Aggregation Feature Support

Expand Down Expand Up @@ -222,6 +357,7 @@ connections are made over HTTPS.
[google-cloud-storage]: https://cloud.google.com/storage
[ref-caching-using-preaggs-build-strats]: /docs/pre-aggregations/using-pre-aggregations#pre-aggregation-build-strategies
[ref-schema-ref-types-formats-countdistinctapprox]: /reference/data-modeling/measures#type
[ref-oidc-overview]: /admin/deployment/oidc
[self-preaggs-batching]: #batching
[snowflake]: https://www.snowflake.com/
[snowflake-docs-account-id]:
Expand Down
58 changes: 54 additions & 4 deletions docs-mintlify/admin/deployment/oidc/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -100,12 +100,14 @@ deployments in the tenant.
| **Audience** | The JWT `aud` claim value. Pre-set for `AWS` (`sts.amazonaws.com`) and `Azure` (`api://AzureADTokenExchange`). For `GCP` you supply the **Workload Identity Federation provider resource path** (`//iam.googleapis.com/projects/.../providers/...`) — there is no global GCP audience. User-supplied for `Custom`. |
| **Name** | A short, filesystem-safe slug that becomes part of the token file name (`cube_cloud_token_<name>`). |
| **Subject Claim Format** | Template for the JWT `sub` claim. Defaults to `cube:deployment:{deployment_id}` if left empty. |
| **Custom Claims** | Optional extra JWT claims emitted verbatim in every minted token — see [Custom claims](#custom-claims) below. |
| **Target Env Var** | Custom audience type only: env var Cube populates with the token file path in every execution context — see [Custom token configs](#custom-token-configs) below. |

You can have one config per well-known audience type (AWS, GCP, Azure) plus
any number of custom configs for tools like Snowflake or Databricks. A
deployment that integrates with several clouds simultaneously gets a separate
token file per audience — the Cube runtime picks the right one based on which
SDK is making the call.
any number of custom configs for tools like [Snowflake][ref-oidc-snowflake]
or Databricks. A deployment that integrates with several clouds
simultaneously gets a separate token file per audience — the Cube runtime
picks the right one based on which SDK is making the call.

### Subject claim format

Expand Down Expand Up @@ -160,6 +162,53 @@ a window where both formats are in flight.

</Note>

### Custom claims

Some token consumers authorize through claims beyond `iss`/`sub`/`aud`. The
canonical example is [Snowflake External OAuth][ref-oidc-snowflake], which
grants session roles exclusively through the `scp` claim — a token without
it authenticates but fails role authorization. The **Custom Claims** field
on a token config adds such claims to every token minted for that audience.

Each claim is a name plus a value. Values are emitted as strings; to emit
an array claim, comma-separate the values (a trailing comma forces a
one-element array). For example, claim `scp` with value `session:role-any`
covers the Snowflake case.

Custom claims are deliberately constrained:

- The registered JWT claims (`iss`, `sub`, `aud`, `exp`, `iat`, `nbf`,
`jti`) and anything under the `cube:` namespace cannot be overridden —
those carry the token's identity semantics and stay authoritative.
- Values are strings or arrays of strings only, with bounded lengths.

Like subject-format changes, custom-claim changes take up to one hour to
fully propagate as cached tokens expire.

### Custom token configs

For `Custom` audiences, the driver or SDK on the other side reads the token
from a file. Where that file lives depends on **where the deployment is
running** — deployed pods, a dev-mode worker, and a test-connection probe
each keep it in a different place — so the path must never be hard-coded.

The **Target Env Var** field solves this: name the env var the consumer
reads (e.g. `CUBEJS_DB_SNOWFLAKE_OAUTH_TOKEN_PATH` for the Snowflake
driver), and Cube sets it to the correct token-file path in every context.
Leave `CUBEJS_DB_USER` / `CUBEJS_DB_PASS` (or the equivalent secret) unset
and don't write the path by hand. Well-known audiences (AWS/GCP/Azure)
don't need this — their SDKs read fixed, standard env vars that Cube
already populates.

The example below — a custom config for the Snowflake driver — combines
both fields: a `scp` custom claim (which Snowflake requires for role
authorization) and a Target Env Var of `CUBEJS_DB_SNOWFLAKE_OAUTH_TOKEN_PATH`.
The [Snowflake data source page][ref-oidc-snowflake] walks through the full setup.

<Frame>
<img src="/images/admin/deployment/oidc/snowflake-token-config-dialog.png" alt="Edit OIDC Token Config dialog in Cube Cloud for a custom Snowflake audience: Custom audience type, the Snowflake account URL as the custom audience, a Target Env Var of CUBEJS_DB_SNOWFLAKE_OAUTH_TOKEN_PATH, and a custom claim scp set to session:role-any." />
</Frame>

## Trust mechanism

The receiving system trusts Cube tokens by referencing two things:
Expand Down Expand Up @@ -319,6 +368,7 @@ which resource and when, without ever issuing a long-lived credential.
[ref-oidc-aws]: /admin/deployment/oidc/aws
[ref-oidc-gcp]: /admin/deployment/oidc/gcp
[ref-oidc-azure]: /admin/deployment/oidc/azure
[ref-oidc-snowflake]: /admin/connect-to-data/data-sources/snowflake#oidc-workload-identity
[ref-env-vars]: /admin/deployment#environment-variables
[ref-data-sources]: /admin/connect-to-data/data-sources
[ref-sub-editor]: /admin/deployment/oidc#subject-claim-format
Expand Down
7 changes: 3 additions & 4 deletions docs-mintlify/docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@
]
},
"docs/explore-analyze/charts/custom",
{
{
"group": "Configure charts",
"root": "docs/explore-analyze/charts/configuration/index",
"pages": [
Expand Down Expand Up @@ -162,7 +162,6 @@
"docs/data-modeling/access-control/context"
]
},

{
"group": "Dynamic Data Models",
"root": "docs/data-modeling/dynamic/index",
Expand Down Expand Up @@ -481,7 +480,7 @@
"reference/data-modeling/cube-package",
"reference/data-modeling/cube_dbt",
"reference/data-modeling/lkml2cube"
]
]
}
]
},
Expand Down Expand Up @@ -1012,4 +1011,4 @@
"destination": "/admin/deployment/scalability"
}
]
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.