Snowflake で SERVICE ユーザを作成してキーペア認証する

## 2025年4月からパスワード認証時の MFA が必須に
2024年7月のアップデート[^202407update] で `USER` オブジェクトに `TYPE` プロパティが追加され、これまで「ユーザ」と一括りだったものが、人間に紐づくユーザ (`PERSON`) とシステムに紐づくユーザ (`SERVICE`) とが区別して登録できるようになった。

[^202407update]: [July 15-17, 2024 — 8.26 Release Notes | Snowflake Documentation](https://docs.snowflake.com/en/release-notes/2024/8_26)

さらに、現在 (2025年2月時点) では Snowsight にサインインすると以下のようなメッセージが表示される。

> `Security Alert` **Upcoming security changes**  
> To make your accounts more secure, starting in April 2025, Snowflake will require that all human users who sign in with passwords use MFA. This change will not affect SSO sign-ins or service users. Read our [blog](https://www.snowflake.com/blog/blocking-single-factor-password-authentification/) post to understand what you should do to ensure that there are minimal disruptions for your users.

2025年4月からはパスワード認証でサインインする際に MFA が必須となる[^mfa]とのこと。

[^mfa]: [Snowflake Will Block Single-Factor Password Authentication by November 2025](https://www.snowflake.com/en/blog/blocking-single-factor-password-authentification/)

「認証ポリシー」や「LEGACY_SERVICE ユーザ」による回避方法も用意されているが、いずれも移行作業の時間稼ぎのために用意されている方法であり、パスワード認証時の MFA 必須化から免れることはできない。

これに伴って、各ユーザについて以下のように対応する必要がある。

- 人間が使用している `USER` オブジェクト (Snowsight 利用など) の場合
    - MFA を有効化する
- システム/プログラムが使用している `USER` オブジェクト (API 利用など) の場合
    - `USER` オブジェクトの `TYPE` を `SERVICE` に設定し、パスワード認証ではない別の認証方式に切り替える

人間の方は、利用者全員に「MFA を有効化しろよ」とアナウンスすれば良いだけなのでまだ難しくない。

問題は **「何らかのシステムからパスワード認証で Snowflake にアクセスしている場合」** で、パスワード認証ではない **別の認証方式に切り替える必要がある。** 

この「別の認証方式」にはいくつかあるらしいが、**この記事ではキーペア認証を使用して Snowflake にアクセスする方法について扱っていく。** (※ この記事ではキーペアのローテーションについては扱わないので、必要であれば各自で調べていただきたい。)

## キーペア認証で Snowflake にアクセスする手順
基本的には公式ドキュメント[^keypair] 通りにやっていくが、細かい手順を補足したりしている。

[^keypair]: [Key-pair authentication and key-pair rotation | Snowflake Documentation](https://docs.snowflake.com/en/user-guide/key-pair-auth)

### キーペアの作成
`openssl` コマンドを使って RSA キーペアを作成していく。

``` console
$ openssl genrsa 2048 | openssl pkcs8 -topk8 -inform PEM -out rsa_key.p8 -nocrypt
```

秘密鍵 `rsa_key.p8` が作成された。

``` console
$ openssl rsa -in rsa_key.p8 -pubout -out rsa_key.pub
```

公開鍵 `rsa_key.pub` が作成された。

::: note warn
今回の手順では秘密鍵のパスフレーズを設定せずに作成しているが、一部の Snowflake と連携する SaaS (※) などではキーペア認証を設定する際にパスフレーズ設定が必須となっている場合があるため注意されたい。
※ dbt Cloud, Tableau, OpenMetadata など
:::

### サービスユーザを作成し、公開鍵を紐づける
先ほど作成した公開鍵ファイルの内容はそのままだと指定できない。冒頭と末尾にある `-----BEGIN PUBLIC KEY-----` のような行を削除したうえで、改行を消した文字列を用意する。

``` console
$ cat rsa_key.pub | grep -v "PUBLIC KEY" | tr -d '\n'
MIIB...
```

この公開鍵文字列を使って Snowflake のサービスユーザを新規作成する。[^create-user]

[^create-user]: [CREATE USER | Snowflake Documentation](https://docs.snowflake.com/en/sql-reference/sql/create-user)

``` sql:Snowflake
CREATE USER myapp
    TYPE = SERVICE
    DEFAULT_ROLE = PUBLIC
    RSA_PUBLIC_KEY = 'MIIB...'
```

既存のサービスユーザに公開鍵を紐づけたい場合は `ALTER` クエリを使う。

``` sql:Snowflake
ALTER USER myapp SET RSA_PUBLIC_KEY='MIIB...'
```

### Python クライアントから接続する
クライアントから接続する例として、Python (`snowflake-connector-python`) から接続してクエリを実行してみる。

ちなみに `dbt-snowflake` や `terraform-provider-snowflake` を使用する場合の手順は以下の記事がよくまとまっていた。

https://zenn.dev/dataheroes/articles/snowflake-private-key-auth

公開鍵と同様に、秘密鍵の `-----BEGIN PRIVATE KEY-----` などを消して改行をなくした文字列を用意する。

``` console
$ cat rsa_key.p8 | grep -v "PRIVATE KEY" | tr -d '\n'
MIIE...
```

接続オブジェクトを作成する際に、秘密鍵を Base64 デコードしたバイト列を `private_key` 引数に指定することでキーペア認証が利用できる。(※ この例では秘密鍵文字列をハードコーディングしているが、実際にやる際はセキュアな場所に格納しておいて取得するようにしたほうがよい。)

``` python
from base64 import b64decode

import snowflake.connector

SNOWFLAKE_PRIVATE_KEY = "MIIE..."


def get_connection():
    return snowflake.connector.connect(
        account="xxxxxxx-xx00000",
        database="myapp",
        user="myapp",
        private_key=b64decode(SNOWFLAKE_PRIVATE_KEY),
    )
```

正しく設定ができていれば、以下のようにしてクエリが実行できることが確認できると思う。

``` python
with get_connection() as conn:
    with conn.cursor() as cur:
        cur.execute("SELECT COUNT(1) FROM mytable")
        print(cur.fetchone())
```