Cloud Functionsで機密情報の管理にSecret Managerを使ってみた

# はじめに
App Store Connect API からiOSのダウンロード数を取得する実装で証明書を使用することになり、
functionsからApp Store Connectのprivate Keyをクラウド上の読み取れる場所に置く必要がありました。

そこで、GCPのSecret Managerを活用することになったため、Secret Managerとは何か、どんなメリットがあるのかなどをアウトプットします。


# Secret Managerとは

- Secret ManagerはAPIキーやパスワードなどの機密データを集中管理するためのサービス。
- このサービスにパスワードなどの情報をバージョン付シークレットとして登録しておき、他のシステムから「シークレット名、バージョン」を指定して呼び出すことで利用します。
- また、IAMで作成したシークレット毎に権限を設定できるので、細かなアクセス制御が可能。
- [Cloud Functionsのドキュメント](https://cloud.google.com/functions/docs/env-var?hl=ja) によると、シークレット情報の取り扱いのベストプラクティスとして Secret Manager が紹介されています。
- AWSでいう「AWS Secrets Manager」と同機能

https://cloud.google.com/secret-manager

# Cloud KMSとの比較

- これまでだとCloud KMS(Key Manegement System)で作成した鍵などを使用し、個別に認証情報を暗号化/復号化する方法が一般的だった。
- ただ、以下の点でCloud KMSは面倒でした。

:::note warn
・Cloud KMSの鍵で暗号化/復号化する仕組みは　「自分で」　作らないといけない

・認証情報をローテーションする度に暗号化をしないといけない
 → メンテナンスが面倒。システムが増えると・・・

・暗号化した認証情報を　「個別」　管理が必要
→ 使われているのかどうかわからない認証情報が溜まるが、どこかで使われているかもしれないから消せない。
:::

https://cloud.google.com/security-key-management?hl=ja

一方、Secret Managerには下記機能があり、**上記を解決**できそうでした。

:::note info
・シークレット(認証情報)の集中管理ができる

・暗号化/復号化はSecret Manager側でやってくれる

・GCPのサービスなのでIAMでシークレット毎に利用権限（ロール）の設定が可能
     ex/ 「アカウントAにはシークレットα の使用を許可するがアカウントBには許可しない」など

・Audit Logでシークレットの利用履歴を確認できる

・シークレットのバージョン管理が可能
:::

特に、暗号化/復号化はSecret Manager側が行ってくれる部分が、Cloud KMSよりも手軽に利用できそうでした！

## 料金

- Secret Managerは無料のサービスではなかった・・・
- 利用した量に応じて課金が発生します。具体的には

:::note info
・シークレットの個数(バージョン)
・シークレットを呼び出した回数
:::

に比例し、月額料金が決まります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563702/ff708cd6-9348-22d6-5140-deffadcef877.png)


僕の場合、とりあえず

:::note info
・1バージョン
・単一ロケーション
・1日1回のアクセス（呼び出し）
:::

また、ローテーションは考えてないので、$0.06+$0.03/月なので、月10円ぐらいでした。

安い！

## シークレットの作成手順

以下Secret Managerにシークレットを作成する手順。大きく3STEPになります。

1. **シークレットを利用できるサービスアカウントを作成**
2. **シークレットを作成**
3. **サービスアカウントにシークレットを利用できる権限ロールを設定**

### 1/  **シークレットを利用できるサービスアカウントを作成**

下記スクショのように進めると、サービスアカウントを作成できる

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563702/a7abdca4-a9f0-412a-aa0c-fb17ebe36a78.png)


ロールは不要

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563702/1bd9ff86-10f0-2ea4-3ed1-a462f29a96f2.png)


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563702/62d82ab4-71e2-9908-6a8a-9014ef6f792b.png)




### 2/ **シークレットを作成**

Secret Managerにシークレットを登録します。

コンソールの「セキュリティ」→「Secret Manager」を開きます。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563702/bb0a8fd9-a1c8-d81e-c771-bcf2a79bbc60.png)



プロジェクトでSecret Manager APIが有効になっていない場合は、APIを有効にします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563702/581ec84e-bc1c-4fc8-8288-bd6d93fd86bd.png)



「シークレットを作成」をクリック。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563702/8410ca1c-a3e2-746c-cb3e-2eb2b1c1e1c7.png)




テスト用のシークレットを作成します。

- `名前`：sample
- `シークレットの値`：auth-keyが入ったファイルをアップロード。すると、自動でシークレットの値が入ります。
- `レプリケーションポリシー`：`このシークレットのロケーションを手動で管理する`をクリック
    - ロケーションが出てくるので、`asia-northeast1` を選択
- `暗号化`、`有効期限`、`通知`、`ラベル`は、設定しない（何もしない）
    
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563702/3bea14dc-300c-df20-9c90-ac0cae40fe2b.png)

    

### 3/ **サービスアカウントにシークレットを利用できる権限ロールを設定**

- シークレットを作成後、Secret Managerに登録したシークレットを利用するには、サービスアカウントに`Secret Manager のシークレットアクセサー`のIAMロールが必要になるので、ロールを追加します
    
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563702/b6b93dd9-4101-fd6a-a175-23e717cf986e.png)

    

## Cloud Functionsからシークレットを使ってみる

まず、Secret Manager を扱うために必要なモジュールをインストールします。

- Functions 用の環境がない場合、上記の前に`firebase init` 等で事前に環境を生成する。

```
$yarn add @google-cloud/secret-manager
　or 
$npm i @google-cloud/secret-manager
```

```jsx
// index.js

import * as functions from 'firebase-functions';
import { SecretManagerServiceClient } from '@google-cloud/secret-manager';

const f = functions.region('asia-northeast1');

export const helloWorld = f.https.onRequest(async (request, response) => {
 // 環境変数から設定した情報を取得
  const projectId = functions.config().project.id;
  const secretName = functions.config().secret.name;
  const secretVersion = functions.config().secret.version;

  const client = new SecretManagerServiceClient();
  const name = client.secretVersionPath(projectId, secretName, secretVersion);
  const [version] = await client.accessSecretVersion({
    name: name,
  });
  const secretValue = version.payload?.data?.toString();

  response.send(`Hello World` : ${secretValue}`);
    });
```

動作確認

```terminal
$ firebase deploy --only functions
$ curl https://asia-northeast1-YOUR_PROJECT_ID.cloudfunctions.net/helloWorld

> Hello World : some secret value
```

これで取得完了！