Skip to content

Commit 303986f

Browse files
authored
feat(snowflake): credential-based auth, object pickers, and 9 new operations (#6474)
* feat(snowflake): credential-based auth, object pickers, and 9 new operations Replace the per-block host + PAT fields with a Snowflake service-account credential, move the credential picker to the top of the block, back the object fields with metadata-only pickers, and add nine operations. - credential: snowflake-service-account token service account (account host + programmatic access token), verified against the SQL API with the same headers the tools use - selectors: database, schema, table, warehouse, execution role, file format and procedure pickers behind one /api/tools/snowflake/objects route - new operations: unload_data, list_databases, list_schemas, list_tables, alter_warehouse, resume_task, suspend_task, list_query_history, list_copy_history * fix(snowflake): migrate renamed subblock IDs and authenticate before parsing - add SUBBLOCK_ID_MIGRATIONS entries so the renamed object fields map onto their pickers and the removed host/apiKey values are parked - authenticate the caller before contract validation in the selector route, per the API route convention * fix(snowflake): close unload-query breakouts, drop parked secrets, correct docs - assertBalancedQuery now skips // line comments, $$ dollar quoting and rejects ambiguous nested block comments; each hid a paren that let an injected OVERWRITE = TRUE escape the derived table - always emit OVERWRITE so an injected duplicate is rejected by Snowflake rather than silently replacing staged files - _removed_ migration targets now drop the stored value instead of parking it under a dead key, where export scrubbing (which walks the block config) would never clear it - 403 falls back to the shared invalid-credentials message, which names the network policy and SQL API causes Snowflake does not distinguish in the body - correct the network-policy-by-user-type claim: only SERVICE_AGENT is exempt - correct MAX_FILE_SIZE and errorOnly tool descriptions to match the fixed code * fix(snowflake): stop untouched switches emitting clauses; retarget migration - an untouched switch serializes as null, and advanced mode emits every advanced subblock, so alter_warehouse silently sent AUTO_RESUME = FALSE and permanently disabled auto-resume on the warehouse; normalize optional booleans to undefined in tools.config.params - point the subblock migration at the advanced text members: a migrated block has no credential, so a picker cannot hydrate a stored name, and legacy fileFormat values were qualified while the picker lists bare names - add the missing json-object wand type and scope the SQL wand prompt, which promised bindings that unload_data does not accept * fix(migrations): sweep already-parked subblock values; align picker 403 - an earlier version of this migration renamed retired fields into _removed_* keys instead of deleting them, so deployed workflows still hold those values; they match no oldId, so a dedicated sweep clears them for every block type - the picker now treats a Snowflake 403 like a 401: it means a network policy or a disabled SQL API, which the credential validator already reports as a credential problem rather than a bad request * fix(wand): add json-array generation type for array-contract fields The json-object reinforcement tells the model the response must start with { and end with }, which fights any field whose contract is an array. Snowflake's rows, matchColumns and procedureArguments all ask for arrays, so they were being steered toward an object that the JSON parse would then reject. Adds a sibling json-array type that strips fences the same way but reinforces brackets, and points the three array fields at it. bindings and filters are genuine objects and stay on json-object. * fix(snowflake): unload a table, not an inline query The COPY INTO grammar places the source immediately before its copy options, so an inlined query sits one parenthesis from being able to rewrite them. Guarding that means matching Snowflake's tokenizer exactly, and three successive versions of the guard were each defeated: // line comments, $$ dollar quoting, and a bare carriage return, which the scanner did not treat as a line terminator but Snowflake does. Each fix was a guess at a lexer the public docs do not specify. Removes the inline-query source instead of guessing a fourth time. A table name goes through qualifiedIdentifier, which is provably safe. Exporting a query result now means materializing it first — a view, or CREATE TABLE AS SELECT via Execute SQL — which the tool description, the block skill and the docs all say. Also from the final audit: - optionalBoolean accepts the string forms a direct tool call delivers, matching the other boolean readers on this block, and its TSDoc no longer states the serializer rule backwards - the five JSON editors declare language: 'json', so invalid JSON is caught inline instead of at execution - bound the RESULT_SCAN read in SQL, not only by rows_per_resultset - pin every migration target to a live subblock id, for all blocks
1 parent 29cfb85 commit 303986f

68 files changed

Lines changed: 3671 additions & 488 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apps/docs/content/docs/en/integrations/meta.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,7 @@
224224
"smartlead",
225225
"smtp",
226226
"snowflake",
227+
"snowflake-service-account",
227228
"sportmonks",
228229
"sqs",
229230
"square",
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
---
2+
title: Snowflake Programmatic Access Tokens
3+
description: Create a Snowflake programmatic access token and connect it to Sim so workflows can query your account
4+
---
5+
6+
import { Callout } from 'fumadocs-ui/components/callout'
7+
import { Step, Steps } from 'fumadocs-ui/components/steps'
8+
import { FAQ } from '@/components/ui/faq'
9+
10+
A Snowflake programmatic access token (PAT) lets a workflow authenticate to your account over the Snowflake SQL API without a password or a key pair. The token belongs to one Snowflake user. Left unrestricted it can act as any role that user holds; with `ROLE_RESTRICTION` set it is pinned to exactly one.
11+
12+
Sim stores the token alongside your account host as one credential. Once it is added, every Snowflake block picks it from a dropdown — and the block's database, schema, table, warehouse, role, file-format, and procedure fields become pickers that list what the token can actually see.
13+
14+
## Prerequisites
15+
16+
- A Snowflake user you can generate a token for. Generating a token for another user requires the ability to run `ALTER USER` on them.
17+
- Your account host — the `<account_identifier>.snowflakecomputing.com` hostname, for example `myorg-myaccount.snowflakecomputing.com`. Snowsight shows it under **Account details**.
18+
- A network policy covering the user, or an authentication policy that waives the requirement (see below).
19+
20+
<Callout type="warn">
21+
Snowflake's **network policy** requirement varies by user type, and getting it wrong is the most common reason a token is rejected:
22+
23+
- `TYPE = PERSON` — you can generate a token without a network policy, but the user **must** be covered by one to authenticate with it.
24+
- `TYPE = SERVICE` and `TYPE = LEGACY_SERVICE` — a network policy is required to generate **and** to use a token.
25+
- `TYPE = SERVICE_AGENT` — exempt; generate and use freely.
26+
27+
If your account has no network policy, either create one (allowing Sim's egress) or set `NETWORK_POLICY_EVALUATION = ENFORCED_NOT_REQUIRED` on an authentication policy applied to the user.
28+
</Callout>
29+
30+
## Creating the Token
31+
32+
### Option 1 — Snowsight
33+
34+
<Steps>
35+
<Step>
36+
Open **Governance & security****Users & roles** and select the user the workflow should run as
37+
</Step>
38+
<Step>
39+
Under **Programmatic access tokens**, click **Generate new token**
40+
</Step>
41+
<Step>
42+
Give it a name, optionally restrict it to a single role, and set the expiry in days
43+
</Step>
44+
<Step>
45+
Copy the token secret. Snowflake shows it **once**, at creation
46+
</Step>
47+
</Steps>
48+
49+
### Option 2 — SQL
50+
51+
```sql
52+
ALTER USER my_service_user ADD PROGRAMMATIC ACCESS TOKEN sim_workflows
53+
ROLE_RESTRICTION = 'SIM_WORKFLOW_ROLE'
54+
DAYS_TO_EXPIRY = 90;
55+
```
56+
57+
`DAYS_TO_EXPIRY` defaults to 15 days and cannot exceed 365 — an authentication policy can lower that ceiling further via `PROGRAMMATIC_ACCESS_TOKEN_MAX_EXPIRY_IN_DAYS`. **A token can never be non-expiring**, and the value cannot be changed after creation — to extend it, generate a new token and swap the credential in Sim. Plan the rotation when you create it.
58+
59+
Service users (`TYPE = SERVICE`, `LEGACY_SERVICE`, or `SERVICE_AGENT`) **must** set `ROLE_RESTRICTION`, unless an authentication policy exempts them. For person users it is optional but recommended: a restricted token can only ever act as that one role.
60+
61+
<Callout type="info">
62+
If an authentication policy applies to the user, `'PROGRAMMATIC_ACCESS_TOKEN'` must appear in its `AUTHENTICATION_METHODS` list, otherwise the token is refused.
63+
</Callout>
64+
65+
## Adding the Credential to Sim
66+
67+
<Steps>
68+
<Step>
69+
Add a **Snowflake** block to a workflow, open the credential dropdown, and choose to add a programmatic access token
70+
</Step>
71+
<Step>
72+
Enter the **account host** (`myorg-myaccount.snowflakecomputing.com`) and paste the **token**
73+
</Step>
74+
<Step>
75+
Save. Sim verifies the credential by running `SELECT CURRENT_USER(), CURRENT_ACCOUNT(), CURRENT_ROLE()` over the SQL API — a metadata-only statement that needs no warehouse and consumes no credits. A rejected token, an unreachable host, or a blocking network policy each produce a specific error rather than a generic failure.
76+
</Step>
77+
</Steps>
78+
79+
The host and the token are encrypted before being stored, and the token is never returned to the browser — the block sends a credential id and Sim resolves it server-side.
80+
81+
## Using the Credential in Workflows
82+
83+
Select the credential on any Snowflake block. You never enter the host again: every tool derives its endpoint from the host stored on the credential.
84+
85+
With a credential selected, these fields become pickers backed by metadata-only statements:
86+
87+
| Field | Lists | Needs |
88+
| --- | --- | --- |
89+
| Database | `SHOW DATABASES` | credential |
90+
| Schema | `SHOW SCHEMAS IN DATABASE` | database |
91+
| Table | `SHOW TABLES IN SCHEMA` | database, schema |
92+
| Warehouse | `SHOW WAREHOUSES` | credential |
93+
| Execution role | `CURRENT_AVAILABLE_ROLES()` | credential |
94+
| Named file format | `SHOW FILE FORMATS IN SCHEMA` | database, schema |
95+
| Procedure | `SHOW PROCEDURES IN SCHEMA` | database, schema |
96+
97+
Each picker runs as the token's user under its **default** role — not the execution role set on the block — so an empty list is usually a privilege gap rather than an empty account. Switch any field to advanced mode to type a name directly or reference an upstream block's output instead.
98+
99+
<Callout type="info">
100+
**Unload Data exports a table, not a query.** The COPY INTO grammar places the
101+
source immediately before its options, so an inline query would sit one
102+
parenthesis away from being able to rewrite them. To export a query result,
103+
materialize it first — a view, or `CREATE TABLE AS SELECT` via Execute SQL —
104+
then unload that object.
105+
</Callout>
106+
107+
## Rotating and Revoking
108+
109+
A token's expiry is fixed at creation. To rotate, generate a new token on the same user and update the credential in Sim — the old one stays valid until you remove it. `ALTER USER ... REMOVE PROGRAMMATIC ACCESS TOKEN <name>` revokes immediately and cannot be undone.
110+
111+
<FAQ items={[
112+
{ question: "Why a programmatic access token instead of a password?", answer: "The token is scoped to one user, can be restricted to a single role, expires on a schedule you choose, and can be revoked on its own without changing anyone's password or breaking other integrations." },
113+
{ question: "Does the token expire?", answer: "Yes. DAYS_TO_EXPIRY defaults to 15 days and can be set up to 365 at creation. It cannot be changed afterwards, so pick the value you want up front and plan a rotation." },
114+
{ question: "I lost the token — can I see it again?", answer: "No. Snowflake shows the secret only at creation. Generate a new token and update the credential in Sim." },
115+
{ question: "Why does adding the credential fail with an authentication error?", answer: "The three common causes are a token that has expired or been revoked, a user with no network policy (required to authenticate for every type except SERVICE_AGENT, unless an authentication policy waives it), and an authentication policy that omits PROGRAMMATIC_ACCESS_TOKEN from its AUTHENTICATION_METHODS. A wrong account host is reported separately — Snowflake resolves any *.snowflakecomputing.com name, so Sim identifies a mistyped host by the 404 it answers with." },
116+
{ question: "Why is a picker empty?", answer: "The pickers run SHOW statements as the token's user under its default role — the block's execution role is not applied to them. If the objects you expect are visible only to another role, grant the default role usage on them, restrict the token to the role that has access, or type the name in advanced mode." },
117+
{ question: "Does listing objects cost credits?", answer: "No. Every picker and the credential check run metadata-only statements, which Snowflake serves without a running warehouse." },
118+
{ question: "Can one credential reach two Snowflake accounts?", answer: "No. A token is bound to the user in one account, and the credential stores that account's host. Add one credential per account." },
119+
{ question: "How many tokens can a user have?", answer: "Snowflake allows up to 15 active programmatic access tokens per user." },
120+
]} />

0 commit comments

Comments
 (0)