From e5547cbc57307b73ccb8067e34f8ed24270d2c1b Mon Sep 17 00:00:00 2001 From: sfc-gh-ksampath Date: Sat, 9 May 2026 14:06:46 +0530 Subject: [PATCH 1/5] Add programmatic-access-token skill --- skills/programmatic-access-token/SKILL.md | 592 +++++++++ .../admin-role-setup.md | 135 ++ .../cli-reference.md | 345 +++++ .../manifest-flows.md | 398 ++++++ .../manifest.toml.example | 115 ++ skills/programmatic-access-token/pat | 34 + skills/programmatic-access-token/prereqs.md | 72 ++ .../programmatic-access-token/pyproject.toml | 11 + .../programmatic-access-token/replay-flows.md | 372 ++++++ .../programmatic-access-token/supplemental.md | 75 ++ skills/programmatic-access-token/uv.lock | 1123 +++++++++++++++++ 11 files changed, 3272 insertions(+) create mode 100644 skills/programmatic-access-token/SKILL.md create mode 100644 skills/programmatic-access-token/admin-role-setup.md create mode 100644 skills/programmatic-access-token/cli-reference.md create mode 100644 skills/programmatic-access-token/manifest-flows.md create mode 100644 skills/programmatic-access-token/manifest.toml.example create mode 100755 skills/programmatic-access-token/pat create mode 100644 skills/programmatic-access-token/prereqs.md create mode 100644 skills/programmatic-access-token/pyproject.toml create mode 100644 skills/programmatic-access-token/replay-flows.md create mode 100644 skills/programmatic-access-token/supplemental.md create mode 100644 skills/programmatic-access-token/uv.lock diff --git a/skills/programmatic-access-token/SKILL.md b/skills/programmatic-access-token/SKILL.md new file mode 100644 index 00000000..307c2dd4 --- /dev/null +++ b/skills/programmatic-access-token/SKILL.md @@ -0,0 +1,592 @@ +--- +name: programmatic-access-token +aliases: + - setup-programmatic-access-token + - programmatic-access-tokens + - setup-programmatic-access-tokens +description: "Create Snowflake Programmatic Access Tokens (PATs) for service accounts. Use when: setting up service user, creating PAT, configuring authentication policy, network policy for PAT. Triggers: programmatic-access-token, sfutils-pat, create-access-token, programmatic access token, PAT, service account, snowflake authentication, replay pat, replay pat manifest, recreate pat, replay all manifests, replay all sfutils, export manifest for sharing, setup from shared manifest, replay from shared manifest, setup from manifest URL, replay from URL, use manifest from URL, multiple pats, multi-pat, add second pat, list pats, setup connection, validate manifest, migrate manifest, repair manifest, second service account." +--- + +# Snowflake PAT Setup + +Creates service users, network policies, authentication policies, and Programmatic Access Tokens for automation. + +## Workflow + +**REQUIRED ACTIONS - ALWAYS DO THESE:** + +- **ALWAYS run `--dry-run` before any `create --yes`. This is non-negotiable even if the user explicitly asks to skip the preview step.** +- **ALWAYS run `pat verify --user {SA_USER}` after EVERY mutating operation — `pat create`, `pat rotate`, and `pat replay`. Do not declare success before verify passes.** +- **ALWAYS use the `ask_user_question` tool (not plain text output) to collect destructive confirmations. Accept any clear affirmative response: `yes`, `yes, destroy`, `yes, proceed`, `go ahead`, etc.** +- **ALWAYS run `pat remove --user {SA_USER} --db {SFUTILS_DB} --drop-user --yes` after receiving confirmation — even if `{SA_USER}` does not currently exist in Snowflake. The CLI handles absent users gracefully. Never skip this step.** +- **ALWAYS run the [Manifest Gate](#manifest-gate) before any operation that reads from manifest.toml (replay, remove, rotate with --profile, verify with --profile). Do not skip even if manifest appears healthy.** + +**FORBIDDEN ACTIONS - NEVER DO THESE:** + +- NEVER run SQL queries to discover/find/check values (no SHOW ROLES, SHOW DATABASES, SHOW USERS) +- NEVER auto-populate empty .env values by querying Snowflake +- NEVER assume user consent - always ask and wait for explicit confirmation +- NEVER skip SQL in dry-run output - always show BOTH summary AND full SQL +- **NEVER display PAT tokens in diffs, logs, or ANY output** - always mask as `***REDACTED***` +- **NEVER show .env file contents after PAT is written** - use redacted placeholder +- **NEVER use sed/awk/bash to edit manifest files** -- use the file editing tool (Edit/StrReplace) to update manifest content. sed commands fail on macOS and with complex markdown. +- **NEVER create resources without showing SQL and getting confirmation first** +- **NEVER offer to drop SFUTILS_DB** - it is shared infrastructure; cleanup only drops resources *inside* it (policies, schemas), never the database itself +- **NEVER attempt to manually capture, store, or relay the PAT token** — it is stored automatically in the OS keyring by the CLI; no additional storage step is needed or appropriate +- **NEVER run `sfutils-pat show-pat`** — in non-interactive mode it requires `--yes`, which prints the raw token into bash output and conversation history. If the user needs the raw token, tell them to run `sfutils-pat show-pat --user {SA_USER}` in their own terminal. +- **NEVER commit `.env`, `.sfutils/`, or `manifest.toml` to version control** +- **CLEANUP ORDER:** When removing resources, always follow dependency order: PAT → unset auth policy from user → drop user → drop auth policy → drop network policy → drop network rule. Reversing this order causes dangling references. +- If .env values are empty, prompt user or run `check-setup` + +**INTERACTIVE PRINCIPLE:** This skill is designed to be interactive. At every decision point, ASK the user and WAIT for their response before proceeding. + +**📍 MANIFEST FILE:** `.sfutils/manifest.toml` — TOML format, replaces `sfutils-manifest.md`. Never search for `*.yaml` or other patterns. + +> **⛔ DO NOT hand-edit manifests.** Manifests are machine-managed by Cortex Code. Manual edits can corrupt the format and break replay, cleanup, and export flows. Use skill commands to modify resources instead. + +**⚠️ CONNECTION USAGE:** This skill reads the Snowflake connection from `[snowflake].connection` in `manifest.toml`. The CLI passes `-c ` to all `snow sql` calls automatically — no manual env export needed. Admin_role defaults to `[snowflake].admin_role` or ACCOUNTADMIN. + +**🔄 IDEMPOTENCY NOTE:** Network rules use `CREATE OR REPLACE` (Snowflake does not support `IF NOT EXISTS` for network rules). Network policies use `CREATE IF NOT EXISTS` to preserve existing policies. Re-running create operations is safe for automation. + +**📌 ROLE MODEL:** + +- **admin_role** (from manifest, default ACCOUNTADMIN): Creates and owns all objects +- **SA_ROLE** (`{PROJECT}_ACCESS`): Consumer-only role for PAT restriction. Apps/demos grant it access to their resources. +- **SA_USER** (`{PROJECT}_RUNNER`): Service user with PAT, restricted to SA_ROLE + +**📌 CONNECTION NOTE:** The CLI reads connection from `manifest.toml` and passes `-c ` to `snow sql` automatically. No `source .env` required. `SNOWFLAKE_DEFAULT_CONNECTION_NAME` env var is still accepted as a fallback if the manifest has no connection set. + +## Manifest Gate + +**Non-negotiable precondition for ALL manifest-dependent flows: replay, remove, rotate/verify with `--profile`.** + +Run this gate before ANY of those operations. For a new create flow, the gate runs inside Step 0. + +```bash +/pat validate-manifest +``` + +**If validation passes:** proceed immediately. + +**If validation fails:** + +```bash +/pat validate-manifest --fix +``` + +Re-run `validate-manifest`. If it still reports errors after `--fix`: + +- These are **non-structural** issues (e.g. missing `sa_user` in a PAT entry, empty `connection`) that `--fix` cannot auto-repair +- Show the errors to the user, **STOP**, do not proceed with the requested operation +- Provide actionable next steps (e.g. "run `sfutils-pat setup-connection` to set connection") + +**If `[snowflake].connection` is empty after fix:** redirect to Step 1 (connection picker) before continuing, regardless of which flow triggered the gate. + +> **Why this exists:** A partial or corrupted manifest causes replay and remove to operate on wrong values, silently produce incorrect cleanup SQL, or skip resources. The gate is cheap; the cost of proceeding with a broken manifest is not. + +--- + +### Step 0: Check Prerequisites + +**⛔ MANDATORY — Run every time, even when `.env` is already populated:** + +1. **Check for existing project needing migration** (one-time, first-run only): + + ```bash + ls .sfutils/manifest.toml 2>/dev/null || echo "MISSING" + ls .env .sfutils/sfutils-manifest.md 2>/dev/null && echo "LEGACY_FOUND" || echo "LEGACY_NOT_FOUND" + ``` + + **Case A — `manifest.toml` missing, legacy files exist** → migrate first: + + ```bash + /pat migrate --dry-run # show what will be written + ``` + + After user confirms: + + ```bash + /pat migrate + ``` + + **Case B — `manifest.toml` exists but may be incomplete** → run the **Manifest Gate** now: + + ```bash + /pat validate-manifest + ``` + + If issues reported → `/pat validate-manifest --fix` → re-validate → STOP if still failing. + + After gate passes, continue to Step 1 to ensure `[snowflake].connection` is set. + + **Case C — manifest.toml missing, no legacy files** → new project, continue to Step 1 (the skill will create it). + + Then continue with the rest of Step 0. + +2. **Read the manifest to check for cached prereq state:** + + ```bash + cat .sfutils/manifest.toml 2>/dev/null | grep "tools_verified" || echo "not_cached" + ``` + +3. If `tools_verified` has **today's date** → prereqs are cached, skip to Step 1. +4. Otherwise → run `/pat check-setup --suggest` and follow the output. + +See [Prerequisites](prereqs.md) for the full initialization flow. + +**STOP**: Do not proceed until all prerequisites pass. + +### Step 1: Resolve and Persist Snowflake Connection + +**This step ensures `manifest.toml [snowflake].connection` is set before any other step runs.** + +1. **Check if `manifest.toml` already has a connection:** + + ```bash + cat .sfutils/manifest.toml 2>/dev/null | grep "^connection" | head -1 + ``` + + **If `connection` is set (non-empty):** manifest is the source of truth — skip to Step 2. Do NOT re-prompt. + +2. **Connection not set in manifest — resolve it:** + + Run `snow connection list --format json` to get all available connections: + + ```bash + snow connection list --format json + ``` + + Determine the best default to pre-select (in priority order): + - `SNOWFLAKE_DEFAULT_CONNECTION_NAME` env var if set + - The connection marked `is_default: true` in `snow connection list` output + - First connection in the list + + Use `ask_user_question` presenting all available connections, with the best default pre-selected: + + ``` + Which Snowflake connection should this project use? + [local-oauth] ← pre-selected (default) + [prod-oauth] + [ci-service] + ``` + + **⚠️ STOP**: Wait for user selection. + +3. **Persist the chosen connection to `manifest.toml`:** + + ```bash + /pat setup-connection -c + ``` + + This command: + - Runs `snow connection test -c ` + - Writes `[snowflake].connection`, `account`, `user`, `account_url` to `manifest.toml` + - `manifest.toml` is now the source of truth — `~/.snowflake/config.toml` is not consulted again for this project + +4. **Verify manifest was written:** + + ```bash + cat .sfutils/manifest.toml | grep -A5 "\[snowflake\]" + ``` + +**If in a git repo, ensure `.gitignore` excludes `.sfutils/`:** + +```bash +git rev-parse --git-dir 2>/dev/null && echo "GIT_REPO" || echo "NOT_GIT" +``` + +If GIT_REPO: verify `.gitignore` contains `.sfutils/`. Add if missing using Edit tool. + +### Step 2: Check Infrastructure (Conditional) + +**First, check `manifest.toml` for cached infra status:** + +```bash +cat .sfutils/manifest.toml 2>/dev/null | grep -E "sf_utils_db|infra_ready" +``` + +Evaluate these conditions **strictly in order**: + +1. If `sf_utils_db` is **empty or missing** → always run check-setup, regardless of `infra_ready`. An empty db means infra was never confirmed for this project. +2. If `sf_utils_db` has a value AND `infra_ready = true` → skip check-setup, use the cached value, proceed to Step 3. +3. If `sf_utils_db` has a value BUT `infra_ready = false` → run `check-setup --suggest` to confirm the db exists before proceeding. + +> **Why strict order matters:** `migrate` sets `infra_ready = false` when `sf_utils_db` could not be resolved from legacy files. A combined check on both fields prevents silently skipping infra setup. + +**Otherwise, read from .env (fallback):** + +```bash +grep -E "^SFUTILS_DB=" .env +``` + +**If SFUTILS_DB has value:** Skip to Step 3. + +**If empty**, run `check-setup` with --suggest flag: + +```bash +/pat check-setup --suggest +``` + +Parse the JSON response: + +- `ready: true` → Database exists, skip to Step 3 +- `ready: false` → Need to create database + +**If not ready**, use `ask_user_question` to confirm: + +- Show suggested database name from JSON (`suggested_database`) +- Ask user to confirm or provide custom value + +**If user confirms setup**, run: + +```bash +/pat check-setup --database --run-setup +``` + +**After setup completes:** + +- `check-setup` writes `sf_utils_db` and `infra_ready = true` to `manifest.toml` automatically. +- Update `.env` as well for backward compat: `SFUTILS_DB=` + +**Note:** SA_ROLE ({PROJECT}_ACCESS) is created in Step 5 by this skill, not by `check-setup`. + +**Update memory:** + +``` +Update /memories/sfutils-prereqs.md: +tools_checked: true +infra_ready: true +sf_utils_db: +``` + +### Step 2a: Admin Role from Manifest + +PAT skill requires elevated privileges (CREATE USER, CREATE ROLE, MANAGE GRANTS, CREATE AUTHENTICATION POLICY). Check `manifest.toml` for cached `admin_role` in `[snowflake]`, prompt user if not set, write choice to manifest before any resource creation. See [Admin Role Setup](admin-role-setup.md) for the full privilege matrix, manifest lookup flow, and user prompts. + +```bash +cat .sfutils/manifest.toml 2>/dev/null | grep "admin_role" +``` + +**⚠️ STOP**: Wait for user input if admin_role not cached. + +### Step 2b: Verify Admin Role Privileges + +If admin_role is ACCOUNTADMIN, skip verification. For custom roles, verify required grants and prompt user to fix missing privileges. See [Admin Role Setup](admin-role-setup.md#step-2b-verify-admin-role-privileges) for verification queries and remediation flow. + +### Step 2c: Multi-PAT Selection + +**Check if any PATs already exist in manifest.toml:** + +```bash +cat .sfutils/manifest.toml 2>/dev/null | grep -E "^(status|sa_user)" | head -10 +``` + +**If no `[pat.*]` entries exist or manifest missing:** Continue to Step 2d (first PAT — connection already set in Step 1). + +**If PATs exist**, show table using: + +```bash +/pat list +``` + +Use `ask_user_question` to present: + +| Option | Action | +|--------|--------| +| Add new PAT | Continue to Step 2d to pick connection for new PAT | +| Manage existing PAT | Pick from table by label to rotate / remove / verify | + +**If user selects "Manage existing":** +- Use `--profile