【Snowflake】パスワード認証のMFAが必須化されるのでキーペアについてまとめてみた

# はじめに
Snowflakeの仕様変更でパスワード認証はなにも対処しなければデフォルトでは2025年4月まで（遅延適用で8月まで）でMFAが必須となるようです。
EUC等でパスワード認証接続している場合に不都合が生じるので、代替となりうるキーペア認証についてまとめてみました。

:::note warn
注意事項
2024年12月時点での情報で書いています。
変更の影響が大きいと思いますので時期がずれる可能性もあるかなと思っています。
:::


# この記事で書くこと
・ユーザのTYPEと認証方式についてのおさらいとSnowflake仕様変更時期
・キーペア認証についての概要
・キーペアの具体的な設定方法
・キーペア認証接続のSampleコード（PythonとExcel-VBA×ODBC）

# ユーザのTYPEと認証方式についてのおさらいとSnowflake仕様変更時期
### ユーザのTYPEについて
ユーザのTYPEには以下の3種類がある。
* PERSON
　　・・人間ユーザ用（defaultはnullでPERSONと同等）
* SERVICE
　　・・システム接続用
* LEGACY_SERVICE 
　　・・システム接続用だが、完全にSERVICEと同じ利用範囲で使えないもののための一時的な救済措置用と思われる。2025年11月に廃止されてSERVICEに移行される予定

TYPEごとの利用可能なクライアントは以下の通り
| TYPE | snowsight | システム接続 |
|--|--|--|
|PERSON| ○ | ○ |
|SERVICE|-|○|
|LEGACY_SERVICE|○|○|


### ユーザの認証方式について
ユーザのTYPEごとに設定可能な認証方法は以下の通り
| 認証方式 | PERSON | SERVICE | LEGACY_SERVICE(廃止予定) |
|:--|-----------|------------|------------|
| MFA+SSO | ○       | -       | -        |
| MFA+パスワード | ○     | -      | -       |
| SSO | ○       | -        | ○（廃止予定）         |
| キーペア | ○         | ○          | ○（廃止予定）           |
| パスワード | ○（廃止予定）| ○（廃止予定）       | ○（廃止予定）       |

### 廃止予定時期
PERSONのパスワードのみでの認証廃止はデフォルトで2025年4月、適用を遅延させれば8月。
SERVICEのパスワード認証廃止は2025年11月

# キーペア認証についての概要
### キーペア認証とは
ユーザが秘密鍵と公開鍵を作成し、公開鍵をSnowflakeユーザに登録したのちに秘密鍵を使ってSnowflakeに接続できるようになる。
### キーペアを使用するセキュリティ以外のメリット
* SSOとの併用
シングルサインオン（以下SSO）認証との併用が可能で、これまで例えばSnowsightへの接続はSSO認証ユーザ、PythonやODBC等からのシステム接続はパスワード認証ユーザ、と分けていたケースも１ユーザで両方を可能にできる。（＝ユーザごとの権限管理が楽になる）ちなみに1ユーザでSSO認証とパスワード認証は併用できない。
* 公開鍵のローテーション
Snowflakeユーザに２つの公開鍵を登録ができ、定期的なキーのローテーションをする必要がある場合もシステム停止なしでキーの入れ替えが可能

### キーペア運用の考慮
大抵の場合、ユーザには自分のユーザをALTER USERする権限はないのでユーザのOWNERSHIPを持つ管理者に公開鍵の登録をお願いする必要がある。
大抵の場合以下のような運用になるはず。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2964512/7e2e3115-c5e7-85dc-6e44-6877e3a6a7c5.png)


# キーペアの具体的な設定方法例
### （Snowflake利用ユーザ）事前準備
OpenSSLのインストール
以下のサイトからOpenSSLのモジュールをダウンロードしてインストール
https://slproweb.com/products/Win32OpenSSL.html

インストールされたOpenSSLのbinフォルダを環境変数のPathに通しておく
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2964512/9f19ff8a-4d90-050e-54d1-2ee3f56d3c33.png)

コマンドプロンプトからインストールされたことを確認
```
openssl version
```
（実行例）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2964512/d0350313-fab9-dd41-4ec0-90b63f98d6b6.png)


### （Snowflake利用ユーザ）キーペアの作成
```
# 秘密鍵ファイルの作成
openssl genrsa 2048 | openssl pkcs8 -topk8 -v2 des3 -inform PEM -out 秘密鍵ファイル名
# 公開鍵ファイルの作成
openssl rsa -in 秘密鍵ファイル名 -pubout -out 公開鍵ファイル名
```
（実行例）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2964512/55bb9c1d-1a93-b290-cb83-b40c4ca9779e.png)
　↓ キーペアが出来上がる
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2964512/02eb64bf-ab41-6866-21a7-0f39f9d3c6d9.png)

### (Snowflakeユーザ管理者)
Snowflakeユーザから公開鍵ファイルを受け取り、テキストエディタで開き、公開鍵の取得（改行コードは除去する）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2964512/45b41161-c7fe-7598-6352-32657ef59b17.png)
```
ALTER USER ユーザ名 SET RSA_PUBLIC_KEY=’公開鍵テキスト（改行コード除去後）’;
```

### 参考：キーペアのローテーション方法
手順１. RSA_PUBLIC_KEYとRSA_PUBLIC_KEY_2の登録状況を確認
``` SQL
DESC USER ユーザ名;
SELECT "property" , "value" 
  FROM TABLE(RESULT_SCAN(LAST_QUERY_ID())) 
 WHERE "property" LIKE 'RSA%';
```
　↓
手順２. RSA_PUBLIC_KEY_2に上記で確認したRSA_PUBLIC_KEYの公開鍵情報をコピー
``` sql
ALTER USER ユーザ名 SET RSA_PUBLIC_KEY_2='上記RSA_PUBLIC_KEYの出力結果'
```
　↓
手順３. 上のキーペア作成・登録手順にてRSA_PUBLIC_KEYに新しいキーを登録、接続可能であることを確認する（この時点では両方のキーで接続可能）

！注意点：2つのキーを登録していても、Snowflake側でどちらのキーで接続しているかを判別する方法は現状では存在しない
　↓
手順４. ユーザのRSA_PUBLIC_KEY_2を削除
``` sql
ALTER USER ユーザ名 UNSET RSA_PUBLIC_KEY_2;
```



# キーペア認証接続のSampleコード

### Python

（必要に応じて）Python ライブラリをインストール
``` python
pip install cryptography
```

接続サンプルコード
``` python
import snowflake.connector
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization

private_key_file = r"C:/temp/rsa_private_key.p8" # ★秘密鍵ファイルPath
with open(private_key_file, "rb") as key:
    p_key= serialization.load_pem_private_key(
        key.read(),
        password="xxxxxxxx".encode(), # ★秘密鍵のパスフレーズ
        backend=default_backend()
    )
 
pkb = p_key.private_bytes(
    encoding=serialization.Encoding.DER,
    format=serialization.PrivateFormat.PKCS8,
    encryption_algorithm=serialization.NoEncryption())
 
print(f"■Snowflake接続")
conn = snowflake.connector.connect(
    user='oka_data', # ★USERのLOGIN名(≠USER名)を指定
    account='xxxxxx-xxxxxx', # ★組織名-アカウント名
    private_key=pkb
    )
 
curs = conn.cursor()
 
print(f"■接続情報確認")
sql_text = """
SELECT 'CURRENT_TIMESTAMP' AS ENV ,TO_CHAR(CURRENT_TIMESTAMP,'yyyy/mm/dd hh24:mi:ss') AS VALUE UNION ALL
SELECT 'CURRENT_ACCOUNT_NAME', CURRENT_ACCOUNT_NAME() UNION ALL
SELECT 'CURRENT_USER'     , CURRENT_USER()
"""
curs.execute(sql_text)
sql_result = curs.fetch_pandas_all()
print(sql_result)
```

### Excel-VBA×ODBC（接続設定箇所のみ）
``` vb
    ' 接続文字列の定義
    Dim conn_str As String
    Dim private_key_file As String
    private_key_file = "C:\temp\rsa_private_key.p8"
    conn_str = "Provider=MSDASQL;" & _
               "DSN=xxxxxxxx;" & _
               "UID=oka_data;" & _
               "AUTHENTICATOR=SNOWFLAKE_JWT;" & _
               "PRIV_KEY_FILE=" & private_key_file & ";" & _
               "PRIV_KEY_FILE_PWD=xxxxxxxx"

    ' Snowflake接続
    Dim conn As New ADODB.Connection
    conn.ConnectionTimeout = 900
    conn.CommandTimeout = 900
    conn.Open conn_str
```
R×ODBCでも同様に以下のオプションを追加することで接続可能であることを確認済
・AUTHENTICATOR
・PRIV_KEY_FILE
・PRIV_KEY_FILE_PWD

# おわりに
何となく抵抗のあったキーペア認証も実際にやってみると簡単です。
4月を迎える前に乗り換えてしまいましょう！