---
name: setup-tundri-snowflake-account
description: Create a new Snowflake account for a client, configure the admin user with MFA, and bootstrap the permifrost service user. Use when onboarding a new client with a Snowflake trial, setting up the initial admin user, or bootstrapping permifrost for the first time.
disable-model-invocation: true
---

# Set Up a New Snowflake Account

Create a new Snowflake account for a client, configure the admin user, and bootstrap the permifrost service user. Use this skill when onboarding a new client that needs a fresh Snowflake account.

## Context

This skill covers the manual Snowflake setup that must happen before tundri can manage the account. After this, all further changes are handled declaratively through `permifrost.yml`.

The `permifrost` user is the only user you create manually. All other users (service and human) will be created by tundri when it runs for the first time.

## Prerequisites

- A web browser to access the Snowflake sign-up page
- `op` CLI installed and signed in (see [1password skill](../../../../../security/1password/))
- Access to the client's 1Password vault
- `openssl` installed on your system
- `python3` available (used internally by the key generation script)
- **The `<client>-snowflake` repository already created** (via the [setup-tundri-repository](../setup-tundri-repository/) skill) — this must be done first so `permifrost.yml` exists for the public key

### Prerequisites Check

Run these commands to verify your environment is ready:

```bash
# Check you're running in WSL (required for op CLI integration)
uname -r  # should contain "microsoft" or "WSL"

# Check op CLI is installed and signed in
op whoami

# Check openssl is installed
openssl version

# Check python3 is available
python3 --version
```

All commands should succeed before proceeding. If `op whoami` fails, sign in with `eval $(op signin)`.

## Steps

**Checklist:**

- [ ] 1. Create the Snowflake account
- [ ] 2. Store admin credentials in 1Password
- [ ] 3. Generate the key pair for the `permifrost` service user
- [ ] 4. Run initial admin SQL commands
- [ ] 5. Set a reminder for credit card setup

### 1. Create the Snowflake account

Go to the [Snowflake sign-up page](https://signup.snowflake.com/) and create a new trial:

- **Region:** Germany (or another EU country if Germany is not available, e.g. Amsterdam on Azure)
- **Edition:** Standard
- **First user name:** `admin`
- **Email:** Use the client's `data@<client_domain>` email account whenever possible. Otherwise, use your Gemma email.

The `admin` user will automatically receive the `accountadmin` role from Snowflake.

### 2. Store admin credentials in 1Password

Store the admin credentials in 1Password **immediately after signup**, before continuing. You will need the account URL and account identifier from the confirmation email (the account identifier is the subdomain part, e.g. `XZNTLOV-ZN11643` from `https://xzntlov-zn11643.snowflakecomputing.com`).

Use the `op` CLI to create the 1Password entry (see [1password skill](../../../../../security/1password/) for full reference). Because `op item create` with assignment statements fails when stdin is not a TTY (e.g. under Claude Code), use the JSON pipe approach:

```bash
python3 -c "
import json
print(json.dumps({
    'title': '<Client Name> - Snowflake: admin',
    'category': 'LOGIN',
    'vault': {'id': '<vault_id>'},
    'urls': [{'primary': True, 'href': 'https://<account_identifier>.snowflakecomputing.com'}],
    'fields': [
        {'id': 'username', 'type': 'STRING', 'label': 'username', 'value': 'admin'},
        {'id': 'password', 'type': 'CONCEALED', 'label': 'password', 'value': '<password>'},
        {'id': 'email', 'type': 'STRING', 'label': 'email', 'value': '<email>'},
        {'id': 'account_identifier', 'type': 'STRING', 'label': 'account_identifier', 'value': '<ACCOUNT_IDENTIFIER>'},
    ]
}))
" | op item create -
```

To find the vault ID: `op vault get "<vault_name>" --format json | python3 -c "import json,sys; print(json.load(sys.stdin)['id'])"`

The item title follows the naming convention `<client name> - <tool name>: <detail>` (e.g. `Acme - Snowflake: admin`).

Verify the item was created:

```bash
op item list --vault "<client_vault>" | grep -i "snowflake"
```

The `admin` user is a `person` type user and requires Multi-factor Authentication (MFA). Configure MFA (passkey recommended) and store the MFA information in the same 1Password entry (this step is manual — use the 1Password desktop app to add the MFA token).

### 3. Generate the key pair for the `permifrost` service user

Before running the admin SQL, you need the public key for the `permifrost` user. Use the [snowflake-generate-keypair](../snowflake-generate-keypair/) skill to generate keys in memory and store them directly in 1Password (the passphrase and private key never touch disk):

```bash
/path/to/snowflake-generate-keypair/scripts/generate_keypair.sh \
    --client <client_name> \
    --user permifrost \
    --op-vault "<client_vault>" \
    --op-title "<Client Name> - Snowflake: permifrost"
```

This generates the key pair entirely in memory, stores both keys and an auto-generated passphrase in 1Password, and prints only the public key (which you need for step 4 and for `permifrost.yml`).

The private key and passphrase can be retrieved later from 1Password for [setup-tundri-local-dev](../setup-tundri-local-dev/) (`.env` configuration) and [setup-tundri-cicd](../setup-tundri-cicd/) (repository secrets).

**Alternative (without 1Password integration):** Generate keys manually with openssl and store them in 1Password yourself. See the [snowflake-generate-keypair](../snowflake-generate-keypair/) skill for details.

### 4. Run initial admin SQL commands

Log in to Snowflake as `admin` and execute the following SQL commands **one by one** (not as a batch). Snowflake needs to switch roles between commands.

```sql
-- Set the type of admin user
USE ROLE accountadmin;
ALTER USER admin SET type = 'person';
ALTER USER admin SET default_role = 'accountadmin';

-- Create warehouse for Permifrost and grant usage
USE ROLE sysadmin;
CREATE WAREHOUSE admin;
GRANT USAGE ON WAREHOUSE admin TO ROLE securityadmin;
GRANT USAGE ON WAREHOUSE admin TO ROLE sysadmin;

-- Create permifrost user, assign public key and grant needed roles
USE ROLE securityadmin;
CREATE USER permifrost default_role = 'securityadmin', default_warehouse = 'admin', type = 'service';
ALTER USER permifrost SET rsa_public_key = 'MIIBIjANBgkqh...';  -- paste the public key content from step 3 (line breaks are OK)
GRANT ROLE securityadmin TO USER permifrost;
GRANT ROLE sysadmin TO USER permifrost;

-- Create permifrost database
USE ROLE sysadmin;
USE WAREHOUSE admin;
CREATE DATABASE permifrost;

-- Enable MFA caching for dbt (not related to Permifrost, but required for the overall setup)
USE ROLE accountadmin;
ALTER ACCOUNT SET ALLOW_CLIENT_MFA_CACHING = TRUE;

-- Restrict Cortex AI cross-region inference to EU only (required for EU data residency)
-- New accounts created after 2026-03-09 default to ANY_REGION, which allows processing outside the EU.
-- Use AWS_EU for AWS-hosted accounts or AZURE_EU for Azure-hosted accounts.
USE ROLE accountadmin;
ALTER ACCOUNT SET CORTEX_ENABLED_CROSS_REGION = 'AWS_EU';  -- replace with AZURE_EU for Azure accounts

-- Drop default objects that new Snowflake accounts come with
USE ROLE accountadmin;
DROP WAREHOUSE IF EXISTS compute_wh;
DROP WAREHOUSE IF EXISTS snowflake_learning_wh;
DROP DATABASE IF EXISTS snowflake_learning_db;
DROP ROLE IF EXISTS snowflake_learning_role;
```

**Important:** In the `ALTER USER permifrost SET rsa_public_key` command, paste the full public key content from step 3. You can paste it with line breaks.

Also add the public key to the `permifrost` user's `rsa_public_key` field in `permifrost.yml` in the `<client>-snowflake` repository (see [snowflake-generate-keypair](../snowflake-generate-keypair/) step 2).

### 5. Set a reminder for credit card setup

**Manual step:** Remind the user to create a calendar event or ticket to ask the client to add their credit card to Snowflake before the trial period ends (typically 30 days from signup). Do not automate this — just inform the user and let them handle it.

## Validation

- [ ] You can log in to Snowflake as `admin` with MFA
- [ ] The `admin` warehouse exists and is usable
- [ ] The `permifrost` user exists with `securityadmin` and `sysadmin` roles
- [ ] The `permifrost` user has the correct public key set
- [ ] The `permifrost` database exists
- [ ] `CORTEX_ENABLED_CROSS_REGION` is set to `AWS_EU` or `AZURE_EU` (not `ANY_REGION`)
- [ ] The default objects (`compute_wh`, `snowflake_learning_wh`, `snowflake_learning_db`, `snowflake_learning_role`) have been dropped
- [ ] Admin credentials and MFA are stored in 1Password
- [ ] The permifrost key pair is stored in 1Password
- [ ] The public key is added to `permifrost.yml` in the `<client>-snowflake` repository
- [ ] A reminder is set for credit card setup

## Troubleshooting

### Error: Warehouse 'COMPUTE_WH' does not exist or not authorized

This can happen during initial tundri runs if the default warehouse was not dropped. Solve by running:

```sql
USE ROLE accountadmin;
DROP WAREHOUSE IF EXISTS compute_wh;
```

### Error: JWT token is invalid

This indicates a mismatch between the private key being used and the public key registered for the user. Verify the public key in `permifrost.yml` matches the generated `.pub` file, and re-run:

```sql
USE ROLE securityadmin;
ALTER USER permifrost SET rsa_public_key = 'MIIBIjANBgkqh...';  -- use the correct public key
```

## Next steps

1. [snowflake-generate-keypair](../snowflake-generate-keypair/) — generate keys for remaining service users (airflow, dbt, etc.)
2. [setup-tundri-cicd](../setup-tundri-cicd/) — configure repository secrets and CI/CD workflows
3. [setup-tundri-local-dev](../setup-tundri-local-dev/) — configure local environment and run tundri for the first time
