Snowflakeのキーペア認証をFargateにて利用する方法

## はじめに
この記事では**Snowflake**をキーペア認証にて接続する方法と共に**Fargate**にて利用する方法について紹介したいと思います．

###そもそもSnowflakeとは
公式ページ

https://www.snowflake.com/

Snowflakeとは**DWH（データウェアハウス）**を提供しているサービスです．
似たサービスとしてGCPのBigQueryやAWSのRedShiftが有名かと思います.
(下記は別の方が書いていただいたDWHの各特長と選び方です)

https://speakerdeck.com/cmtamai/dwhyu-san-jia-falsege-te-zheng-toxuan-bifang-snowflaketobigquerytoredshiftto

## 課題点
あらゆるサービスにおいてログイン方法の漏洩は重大なインシデントになりえることです．
Snowflakeの場合，DWHの性質上個人情報や社内の重要データを保存することもあり，データの漏洩は致命傷となりえます．
そのため重要なデータにアクセスする場合には従来の通常認証（ID/PASS）ではセキュリティ的に**脆弱**になります．

## 課題解決策
今回通常認証の代わりにキーペア認証（公開鍵認証）を利用することでセキュリティ面の向上を図ります．

### 実際に行ってみる

まず初めに秘密鍵を生成します．
任意のローカルディレクトリにて下記のコマンドを打つことでrsa_key.p8というファイルを生成します．
その際にパスワードを求められるので，任意のパスワードを入力してください．

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

次にこの秘密鍵を利用して，公開鍵を生成します．
下記のコマンドを打つことでrsa_key.pubというファイルを生成します．

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

このrsa_key.pubの中に公開鍵がかかれていますので，ログインしたいユーザーに割り振ります．
**Snowflakeの画面**に移り下記のコマンドを打つことで割り振ることが出来ます．この時，**セキュリティ管理者**以上の権限を持つユーザーにて操作してください．
(username=snowflakeのユーザー名，publickey=公開鍵の値)

```
alter user <username> set rsa_public_key='<publickey>';
```

（詳細につきましては下記の公式ドキュメントに記載されていますので是非ご覧ください）

https://docs.snowflake.com/ja/user-guide/key-pair-auth.html


これで準備は整いました．あとは，各種コネクタを利用して接続できるか確認します．
下記は公式ドキュメントに少し書き足したソースコードです．

https://docs.snowflake.com/ja/user-guide/python-connector-example.html#using-key-pair-authentication-key-pair-rotation


```python
"""下記は公式ドキュメントからの引用"""
import snowflake.connector
import os
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives.asymmetric import dsa
from cryptography.hazmat.primitives import serialization
with open("<path>/rsa_key.p8", "rb") as key:
    p_key= serialization.load_pem_private_key(
        key.read(),
        password=os.environ['PRIVATE_KEY_PASSPHRASE'].encode(),
        backend=default_backend()
    )

pkb = p_key.private_bytes(
    encoding=serialization.Encoding.DER,
    format=serialization.PrivateFormat.PKCS8,
    encryption_algorithm=serialization.NoEncryption())

ctx = snowflake.connector.connect(
    user='<user>',
    account='<account_identifier>',
    private_key=pkb,
    warehouse=WAREHOUSE,
    database=DATABASE,
    schema=SCHEMA
    )

cs = ctx.cursor()

"""下記は書き足しました"""
cs.execute("SELECT current_version()")
one_row = cs.fetchone()
print(one_row[0])
```

こちらのソースコードを起動（環境変数「PRIVATE_KEY_PASSPHRASE」に設定したパスワードを予め設置しておいてください）し，snowflakeのバージョンが表示されていれば無事接続されています．

## Fargateにて利用する
今回はこのキーペア認証をAWSのFargate<->Snowflake間接続にて利用します！（**本題**）

### 問題点
Fargateはその仕様上**同じマシン**を選択することが出来ず通常の.sshフォルダに秘密鍵を置くやり方を取れません．
（置いたとしてもマシンが変わるため，その都度書き込む必要がある）
また環境変数に秘密鍵を置くことも考えましたが平文にて置くのはさすがにセキュリティ面にて不安でもあります．

### 解決策
AWSのSystemManagerを利用し，秘密鍵を暗号化し環境変数経由にて取得するやり方を取りました．
この方法であれば，タスク情報に環境変数を置くことでその都度書き込む必要がありません．また暗号化されているため平文で置くというリスクを防ぐことも出来ます．

### 実際に行ってみる
#### SystemManager
AWSのSystemManagerから，アプリケーション管理->パラメータストアを開きます．

![SystemsManager_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/949737/e68007a6-1ed0-ce11-8059-40363578e46e.png)

パラメータの作成を選択すると下記のように設定画面が表示されます．

![SystemsManager_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/949737/f201c791-e75e-d56b-ba54-de06663a65de.png)

##### パラメータストアの設定
**名前** -> 環境変数にて挿入する際の名前になりますので，英数字を**推奨**します
**説明** -> 分かりやすい説明文を書いておいた方がいいと思います．
**利用枠** -> 秘密鍵を生成する際に4096bit以下であれば標準，それ以上であれば詳細を選択してください．
**タイプ** -> **安全な文字列にしてください！**
**KMSキーソース** -> 現在のアカウントを選択してください．
**KMSキーID** -> デフォルトのalias/aws/ssmを選択してください．（別のKMSキーIDを利用することも出来ます．）
**値** -> **秘密鍵ファイルの内容をそのまま記載してください．**

以上の設定が終わりましたらパラメータの作成を選択してください．これでSystemsManagerの設定は完了しました．

#### IAM
次にSystemManagerをECSにて利用できるようにIAMの権限を整理します．
各タスクにて利用している**Amazon ECS タスク実行ロール**に以下のポリシーを追加します．
**ssm:GetParameters**
**secretsmanager:GetSecretValue**
**kms:Decrypt**

```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "ssm:GetParameters",
        "secretsmanager:GetSecretValue",
        "kms:Decrypt"
      ],
      "Resource": "*"
    }
  ]
}
```

#### ECS
それでは実際にECSにて利用できるか確認します．

##### 利用するソースコード
前述にて記載したソースコードの違いとして，秘密鍵ファイルから読み込んでいるところを環境変数経由に変更しています．

```python
import snowflake.connector
import os
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives.asymmetric import dsa
from cryptography.hazmat.primitives import serialization

# パラメータストアから秘密鍵呼び出し
key_input = os.getenv('SECRET_KEY')
key_parameter = key_input.encode()

p_key= serialization.load_pem_private_key(
    key_parameter,
    password=os.getenv('PRIVATE_KEY_PASSPHRASE').encode(),
    backend=default_backend()
)

pkb = p_key.private_bytes(
    encoding=serialization.Encoding.DER,
    format=serialization.PrivateFormat.PKCS8,
    encryption_algorithm=serialization.NoEncryption())

ctx = snowflake.connector.connect(
    user='<user>',
    account='<account_identifier>',
    private_key=pkb,
    warehouse=WAREHOUSE,
    database=DATABASE,
    schema=SCHEMA
    )

cs = ctx.cursor()

"""下記は書き足しました"""
cs.execute("SELECT current_version()")
one_row = cs.fetchone()
print(one_row[0])
```

#### 起動する
通常のようにクラスター作成→タスク作成（先ほどポリシーを追加したロールをタスクに割り振ってください）→タスク起動を行います．
この時注意点として，タスクを起動する際に**プラットフォームのバージョン**という設定がありますので，この値を**1.3.0**に選択してください！
（LATESTを利用してタスクを起動する場合はまた別の設定が必要となります．）

![ECS_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/949737/c30e0d40-6489-4b4b-4059-746491917466.png)

またタスク作成時，もしくはタスク起動時に以下のように環境変数を割り振っておいてください！
**PRIVATE_KEY_PASSPHRASE** -> 秘密鍵を生成した際に設定したパスワードを記載
**SECRET_KEY** -> **ValueFrom**にてパラメータストアにて設定した名前をそのまま記載

![ECS_3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/949737/d8ead784-e5ea-9032-aabd-eb18398e997f.png)

これで起動し，下記のようにCloudWatchLLogsにてSnowflakeのバージョンが表示されていれば，
無事SystemManagerを経由して秘密鍵がFargateに送られており，キーペア認証が行われていることが確認できます！

![ECS_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/949737/94a93537-be80-9158-9b47-653fe1883f25.png)

## 最後に
今回はSnowflakeのキーペア認証をFargateにて利用する方法について記載しました．
今後も様々なことについて書いていきたいと思います！