---
name: snowflake-test-keypair-auth
description: Verify and test that a Snowflake user's key-pair authentication is working. Use when testing a connection after deploying a public key, verifying JWT authentication, checking fingerprint matches against RSA_PUBLIC_KEY or RSA_PUBLIC_KEY_2 slots, or debugging key-pair auth issues.
---

# Test Snowflake Key-Pair Authentication

Verify that a Snowflake user's key-pair authentication is working. Use this skill after deploying a new or updated public key via permifrost, to confirm the private key and passphrase stored in 1Password can successfully authenticate. During key rotation, use the fingerprint verification feature to confirm which key slot the key matches.

## Context

After generating a key pair (see [snowflake-generate-keypair](../snowflake-generate-keypair/)) and deploying the public key to Snowflake via permifrost, you should verify that the authentication works end-to-end. This catches issues like mismatched keys, wrong passphrases, or deployment failures before they impact production.

During [key-pair rotation](../snowflake-rotate-keypair/), Snowflake has two active key slots (`RSA_PUBLIC_KEY` and `RSA_PUBLIC_KEY_2`). A basic auth test succeeds if *either* slot matches, which isn't enough to confirm the new key was deployed to the correct slot. The fingerprint verification mode solves this by comparing the local key's fingerprint against a specific slot in Snowflake.

## Prerequisites

- `uv` installed ([installation guide](https://docs.astral.sh/uv/getting-started/installation/))
- `op` CLI installed and signed in (see [1password skill](../../../../security/1password/))
- The key pair has been deployed to Snowflake (permifrost production run completed after merging the public key into `permifrost.yml`)
- You know the Snowflake account identifier (e.g. `qp74822.eu-central-1`) and the username
- For fingerprint verification: the connecting user (or its role) needs permission to run `DESC USER` on the target user (typically `SECURITYADMIN` or `ACCOUNTADMIN`)

## Steps

**Checklist:**

- [ ] 1. Download the private key from 1Password
- [ ] 2. Run the test script
- [ ] 3. Delete the local private key file

### 1. Download the private key from 1Password

Retrieve the private key file from the user's 1Password entry. File attachments in 1Password follow the naming convention `<client>_<username>_snowflake_private_key`:

```bash
op read --out-file /tmp/<client>_<username>_snowflake.p8 \
  "op://<vault>/<item_id>/<client>_<username>_snowflake_private_key"
```

**Note:** If the item title contains special characters (e.g. colons), use the item ID instead of the title in the `op://` URI. You can find the ID with:

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

### 2. Run the test script

Set the required environment variables and run the script. Pass the passphrase as a 1Password secret reference (`op://...`) — the script resolves it via `op read` at runtime, so the secret never appears in the command or its output:

```bash
SNOWFLAKE_ACCOUNT="<account_identifier>" \
SNOWFLAKE_USER="<USERNAME>" \
SNOWFLAKE_KEY_PATH="/tmp/<client>_<username>_snowflake.p8" \
SNOWFLAKE_KEY_PASSPHRASE="op://<vault>/<item_id>/passphrase" \
uv run ${CLAUDE_SKILL_DIR}/scripts/test_keypair_auth.py
```

**Security note:** Never resolve the passphrase inline with command substitution (`SNOWFLAKE_KEY_PASSPHRASE="$(op read ...)"`). The subshell resolves before the command runs, so the actual passphrase value appears in the executed command string — and leaks into terminal history, logs, and AI session context. Always pass the plain `op://` reference and let the script resolve it.

**Example (using item ID to avoid colon issues in title):**

```bash
SNOWFLAKE_ACCOUNT="qp74822.eu-central-1" \
SNOWFLAKE_USER="LUI" \
SNOWFLAKE_KEY_PATH="/tmp/gemma_lui_snowflake.p8" \
SNOWFLAKE_KEY_PASSPHRASE="op://Employee/linip4x5zkgxglsonicqbtcseu/passphrase" \
uv run ${CLAUDE_SKILL_DIR}/scripts/test_keypair_auth.py
```

**Expected output on success:**

```
Snowflake Key-Pair Auth Test
============================
  Account:   qp74822.eu-central-1
  User:      LUI
  Key file:  /tmp/gemma_lui_snowflake.p8
  Encrypted: yes

Connection successful!
  User:    LUI
  Role:    USERROLE_LUI
  Account: QP74822
```

### 2b. Verify key slot during rotation (optional)

During [key-pair rotation](../snowflake-rotate-keypair/), set `SNOWFLAKE_VERIFY_KEY_SLOT` to confirm the key matches a specific slot in Snowflake. This is important because a basic auth test succeeds if either key slot matches — fingerprint verification confirms the *correct* slot was deployed.

Set `SNOWFLAKE_VERIFY_KEY_SLOT` to `1` (for `RSA_PUBLIC_KEY`) or `2` (for `RSA_PUBLIC_KEY_2`):

```bash
SNOWFLAKE_ACCOUNT="<account_identifier>" \
SNOWFLAKE_USER="<USERNAME>" \
SNOWFLAKE_KEY_PATH="/tmp/<client>_<username>_snowflake.p8" \
SNOWFLAKE_KEY_PASSPHRASE="op://<vault>/<item_id>/passphrase" \
SNOWFLAKE_VERIFY_KEY_SLOT="2" \
uv run ${CLAUDE_SKILL_DIR}/scripts/test_keypair_auth.py
```

**Expected output when verifying key slot 2:**

```
Snowflake Key-Pair Auth Test
============================
  Account:    qp74822.eu-central-1
  User:       LUI
  Key file:   /tmp/gemma_lui_snowflake.p8
  Encrypted:  yes
  Verify slot: RSA_PUBLIC_KEY_2

Connection successful!
  User:    LUI
  Role:    USERROLE_LUI
  Account: QP74822

Fingerprint Verification
------------------------
  Local key fingerprint:  Azk1Pq...
  RSA_PUBLIC_KEY_FP: Xb2mNq... (no match)
  RSA_PUBLIC_KEY_2_FP: Azk1Pq... (MATCH)

  Verified: key matches RSA_PUBLIC_KEY_2
```

The script exits with code 0 if the fingerprint matches the expected slot, or code 1 if it doesn't.

**Note:** Fingerprint verification requires the connecting user's role to have permission to run `DESC USER` on the target user. If permissions are insufficient, the script prints a warning and skips the fingerprint check (the auth test result still stands).

### 3. Delete the local private key file

```bash
rm /tmp/<client>_<username>_snowflake.p8
```

## Environment Variables

| Variable | Required | Description |
|---|---|---|
| `SNOWFLAKE_ACCOUNT` | Yes | Account identifier (e.g. `qp74822.eu-central-1`) |
| `SNOWFLAKE_USER` | Yes | Snowflake username |
| `SNOWFLAKE_KEY_PATH` | Yes | Path to private key `.p8` file |
| `SNOWFLAKE_KEY_PASSPHRASE` | No | Passphrase for encrypted keys (empty for unencrypted). Pass a 1Password secret reference (`op://<vault>/<item>/<field>`) — the script resolves it at runtime via `op read`. A literal passphrase also works but is discouraged. |
| `SNOWFLAKE_VERIFY_KEY_SLOT` | No | `1` or `2` — verify key matches `RSA_PUBLIC_KEY` or `RSA_PUBLIC_KEY_2` |

## Troubleshooting

| Error | Cause | Fix |
|---|---|---|
| `JWT token is invalid` | Public key in Snowflake doesn't match private key | Verify permifrost ran successfully after merging. Check that the public key in `permifrost.yml` matches the private key: `op read 'op://<vault>/<item_id>/passphrase' \| openssl rsa -in key.p8 -pubout -passin stdin` (never put the passphrase on the command line) |
| `Incorrect password, could not decrypt key` | Wrong passphrase | Verify the passphrase in 1Password matches the one used during key generation. If mismatched, regenerate the key pair. |
| `Could not decrypt key` / `Password was not given but private key is encrypted` | Passphrase not passed for encrypted key | Ensure `SNOWFLAKE_KEY_PASSPHRASE` is set and not empty. |
| `Failed to connect to DB` (network) | Wrong account identifier or network issue | Verify the account identifier format (e.g. `qp74822.eu-central-1`). Check the account URL in 1Password. |
| `FAILED: key does NOT match RSA_PUBLIC_KEY_2` | New key not deployed to expected slot | Verify permifrost ran after merging. Check `rsa_public_key_2` in `permifrost.yml` has the correct public key content. |
| `WARNING: Could not query fingerprints` | Insufficient permissions for `DESC USER` | The connecting role needs `SECURITYADMIN` or ownership of the user. The basic auth test still passes. |
| `FAILED: could not resolve passphrase reference` | `op` not signed in, or wrong `op://` reference | Run `op whoami` to check the session. Verify the vault/item/field in the reference (use the item ID if the title has special characters). |

## Validation

- [ ] Script connects successfully and prints user, role, and account
- [ ] (If verifying slot) Fingerprint matches the expected key slot
- [ ] Local private key file is deleted after testing
