---
name: snowflake-generate-keypair
description: Generate RSA private/public key pairs for Snowflake service users. Use when creating encrypted or unencrypted keypairs with openssl, storing keys in 1Password, or setting up key-pair authentication for permifrost, airflow, dbt, or other service users.
disable-model-invocation: true
---

# Generate Snowflake Key Pair

Generate RSA private/public key pairs for Snowflake users. Use this skill when setting up a new client (to create keys for the `permifrost` user and other service users) or when adding a new service user that requires key-pair authentication.

## Context

Snowflake service users authenticate using RSA key pairs instead of passwords. Each service user needs its own key pair. The private key is stored securely in 1Password and used by the service (or by tundri for the permifrost user). The public key is registered in `permifrost.yml` under the user's `rsa_public_key` meta field.

## Prerequisites

- `openssl` installed on your system
- `op` CLI installed and signed in (see [1password skill](../../../../../security/1password/))
- `python3` available (used internally by the script for JSON generation)
- Knowledge of which user needs the key pair (e.g. `permifrost`, `airflow`, `dbt_cloud`, `metabase`)

**Always ask the user which 1Password vault to store the key pair in.** Different clients and projects use different vaults.

## Quick Start

```bash
# Generate and store directly in 1Password (keys never touch disk)
${CLAUDE_SKILL_DIR}/scripts/generate_keypair.sh \
    --client <client_name> \
    --user <username> \
    --op-vault "<client_vault>" \
    --op-title "<Client Name> - Snowflake: <username>"

# Unencrypted key (for services that don't support encrypted keys, e.g. Metabase)
${CLAUDE_SKILL_DIR}/scripts/generate_keypair.sh \
    --client <client_name> \
    --user <username> \
    --op-vault "<client_vault>" \
    --op-title "<Client Name> - Snowflake: <username>" \
    --no-encrypt
```

The script:
1. Auto-generates a 40-character passphrase (never printed or written to disk)
2. Generates a 2048-bit RSA private key in PKCS8 format (in memory)
3. Generates the corresponding public key (in memory)
4. Stores username, passphrase, private key, and public key in 1Password
5. Prints only the public key for copy-pasting into `permifrost.yml`

## Steps

**Checklist:**

- [ ] 1. Run the script
- [ ] 2. Add the public key to `permifrost.yml`

### 1. Generate and store

```bash
${CLAUDE_SKILL_DIR}/scripts/generate_keypair.sh \
    --client <client_name> \
    --user <username> \
    --op-vault "<client_vault>" \
    --op-title "<Client Name> - Snowflake: <username>"
```

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

Verify the item was created:

```bash
op item get "<Client Name> - Snowflake: <username>" --vault "<client_vault>"
```

### 2. Add the public key to `permifrost.yml`

Copy the public key content (printed by the script) into the `rsa_public_key` field of the user in `permifrost.yml`:

```yaml
  - <username>:
      can_login: yes
      member_of:
        - <role>
      meta:
        default_role: <role>
        type: service
        rsa_public_key: |
          -----BEGIN PUBLIC KEY-----
          MIIBIjANBgkqh...
          ...full key content...
          -----END PUBLIC KEY-----
```

## Script Options

| Flag | Required | Description |
|---|---|---|
| `--client` | Yes | Client name (used for key file naming in 1Password) |
| `--user` | Yes | Snowflake username |
| `--op-vault` | Yes | 1Password vault name |
| `--op-title` | Yes | 1Password item title |
| `--algorithm` | No | Encryption algorithm (default: `des3`) |
| `--no-encrypt` | No | Generate unencrypted key (for services like Metabase) |

## Manual Generation (Without the Script)

If you need to generate keys without `op` CLI or need local key files:

**Encrypted key:**

```bash
# Generate private key (you will be prompted for a passphrase)
openssl genrsa 2048 | openssl pkcs8 -topk8 -v2 des3 -inform PEM -out <client>_<username>_snowflake.p8

# Generate public key from private key
openssl rsa -in <client>_<username>_snowflake.p8 -pubout -out <client>_<username>_snowflake.pub
```

**Unencrypted key:**

```bash
# Generate private key (no passphrase)
openssl genrsa 2048 | openssl pkcs8 -topk8 -nocrypt -inform PEM -out <client>_<username>_snowflake.p8

# Generate public key from private key
openssl rsa -in <client>_<username>_snowflake.p8 -pubout -out <client>_<username>_snowflake.pub
```

After generating manually, store both key files and the passphrase in the client's 1Password vault (title: `<Client Name> - Snowflake: <username>`), add the public key to `permifrost.yml`, then delete the local files.

## Typical Service Users

When setting up a new client, you will typically generate key pairs for:

| User | Role | Notes |
|---|---|---|
| `permifrost` | `securityadmin` + `sysadmin` | Always required. Created manually in Snowflake before first tundri run. |
| `airflow` | `loader` | For orchestrating data loading pipelines. |
| `dbt_cloud` | `transformer` | For production dbt Cloud runs. Use `dbt` for dbt Core. |
| `dbt_cloud_dev` | `userrole_dev_cloud` | For development dbt Cloud runs. |
| `airbyte` | `loader` | For Airbyte data loading. |
| `metabase` | `reporter` | For BI reporting. Use `--no-encrypt` — Metabase doesn't support encrypted keys. |

Only generate keys for the services the client actually uses.

## Where Each Key Is Used

After generating a key pair, the keys are used in multiple places across the setup process:

| Artifact | Where it goes | Which skill covers it |
|---|---|---|
| **Public key** | `rsa_public_key` field in `permifrost.yml` for the user | [setup-tundri-repository](tundri-permifrost/setup-tundri-repository/) step 8 |
| **Public key** (for `permifrost` user only) | `ALTER USER permifrost SET rsa_public_key = '...'` SQL command | [setup-tundri-snowflake-account](tundri-permifrost/setup-tundri-snowflake-account/) step 4 |
| **Private key** (file path) | `PERMISSION_BOT_KEY_PATH` in local `.env` file | [setup-tundri-local-dev](tundri-permifrost/setup-tundri-local-dev/) step 2 |
| **Private key** (base64-encoded) | `PERMISSION_BOT_PRIVATE_KEY_ENCODED_BASE64` repository secret | [setup-tundri-cicd](tundri-permifrost/setup-tundri-cicd/) steps 2-3 |
| **Passphrase** | `PERMISSION_BOT_KEY_PASSPHRASE` in local `.env` and repository secret | [setup-tundri-local-dev](tundri-permifrost/setup-tundri-local-dev/) + [setup-tundri-cicd](tundri-permifrost/setup-tundri-cicd/) |
| **Both keys + passphrase** | 1Password vault entry for the client | This skill, step 1 |

The private key and passphrase are retrieved from 1Password when needed for [setup-tundri-local-dev](tundri-permifrost/setup-tundri-local-dev/) and [setup-tundri-cicd](tundri-permifrost/setup-tundri-cicd/).

## Example

```bash
${CLAUDE_SKILL_DIR}/scripts/generate_keypair.sh --client acme --user permifrost \
    --op-vault "Acme Corp" --op-title "Acme - Snowflake: permifrost"
```

Output:

```
Snowflake Key Pair Generator (1Password mode)
===============================================
  Client:     acme
  User:       permifrost
  Encrypted:  yes (des3)
  1Password:  Acme - Snowflake: permifrost (vault: Acme Corp)
  Mode:       in-memory (no files written to disk)

Generating keys (in memory)...
  Private key generated (in memory)
  Public key generated (in memory)

============================================================
PUBLIC KEY (copy this into permifrost.yml rsa_public_key):
============================================================
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...
-----END PUBLIC KEY-----

Storing to 1Password...
  Stored in 1Password: Acme - Snowflake: permifrost (vault: Acme Corp)

NEXT STEPS:
  1. Copy the public key above into the rsa_public_key field
     of the 'permifrost' user in permifrost.yml
```

## Validation

- [ ] The key pair is stored in the client's 1Password vault (with passphrase)
- [ ] The public key is added to the correct user's `rsa_public_key` in `permifrost.yml`
