この記事は Firebase Advent Calendar 2020 10日目の記事です。
先日 Cloud Functions for Firebase + Cloud KMS でシークレット情報を扱う という記事を書きました。
今回は、今年 GA になった Secret Manager を使ってみたいと思います。
はじめに
サービスを開発し始めると、外部サービスのシークレットキーや認証用のID/PASSWORDなど、秘匿しなければいけない情報を扱いたいケースが多々あります。
これらの情報はソース管理するべきではない情報であるため、環境変数等を利用して対応するケースも多いかと思います。
Cloud Functions for Firebase にも 環境変数 はありますが、シークレット情報を直接扱うことには適していません。
また、先日記載した Cloud KMS を使う方法には、いくつか難点がありました。
- Cloud KMS は鍵の管理のため、暗号化/復号化処理を自分で実装しないといけないこと。
- 鍵のローテーションに応じて古いバージョンを廃止する場合、再度暗号化が必要なこと。
Secret Manager では、この辺を含めて自動でやってくれる範囲が広く、エンジニアからするとより楽にシークレット情報を取り扱うことができます。
この記事では、Secret Manger を使ってシークレット情報を Cloud Functions for Firebase から取り扱う方法を記載します。
Secret Manager について
Secret Manager とは、GCP で提供されているサービスの一つで、シークレット情報を管理するための安全で便利なストレージシステムです。
Cloud Functionsのドキュメント によると、シークレット情報の取り扱いのベストプラクティスとして Secret Manager が紹介されています。
対象読者
- Cloud Functions for Firebase を利用したことがある方
- GCP の扱いに少しでも慣れている方
GCP コンソール の細かい操作方法は省略している箇所がありますので、GCPを触ったことない方は分かりにくいかもしれません。
Secret Manager の設定
お試しで実践される方は、新規GCPプロジェクトを作成して頂ければと思います。
APIの有効化
Secret Manager を使うためには、API を有効化する必要があります。
GCP コンソール → APIとサービス より、Secret Manager API を有効化します。
シークレットの作成
実際にシークレットを作成してみましょう。
GCP コンソール → セキュリティ → シークレットマネージャー よりシークレットを作成します。
以下を入力して、シークレットを作成 しましょう。
- シークレットの名前
- シークレットの値 (ファイルを直接インポートすることも可能です。)
- リージョン (必要に応じてリージョンを設定してください。)
Cloud Functions for Firebase で復号化処理の作成
必要なモジュールのインストール
Functions 用の環境がない方は、firebase init 等で事前に環境を生成してください。
Secret Manager を扱うために必要なモジュールをインストールします。
npm i @google-cloud/secret-manager
Secret Manager の情報を環境変数に設定します。
// プロジェクトIDはシークレットを作成したGCPのプロジェクトIDを設定します。
$ firebase functions:config:set project.id="YOUR_PROJECT_ID"
// シークレットの作成の際に入力した名前を設定します。
$ firebase functions:config:set secret.name="secret-sample"
// 作成したシークレットのバージョンを設定します。
$ firebase functions:config:set secret.version="1"
設定した内容を確認します。
$ firebase functions:config:get
{
"secret": {
"name": "secret-sample",
"version": "1"
},
"project": {
"id": "YOUR_PROJECT_ID"
}
}
実際の処理を書いていきましょう。
今回も HTTP トリガーとします。
import * as functions from 'firebase-functions';
import { SecretManagerServiceClient } from '@google-cloud/secret-manager';
export const helloWorld = functions
.region("asia-northeast1")
.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}`);
});
デプロイして動作を確認してみましょう。
// デプロイ
$ firebase deploy --only functions
...
// 動作確認
$ curl https://asia-northeast1-YOUR_PROJECT_ID.cloudfunctions.net/helloWorld
Error: could not handle the request
Functions のログをみてみると以下のようにエラーが出ているのが確認できます。
Cloud KMS の時と同様に、IAM で権限が必要なようです。
Error: 7 PERMISSION_DENIED: Permission 'secretmanager.versions.access' denied for resource 'projects/YOUR_PROJECT_ID/secrets/secret-sample/versions/1'
権限の確認と設定
Functions を実行しているサービスアカウントの確認
GCPコンソール → Cloud Functions → helloWorld → 詳細タブ を開きます。
サービスアカウントが記載されています。
YOUR_PROJECT_ID@appspot.gserviceaccount.com というサービスアカウントで実行されていることがわかります。
権限の設定
上記で確認したサービスアカウントに、権限を設定します。
GCP コンソール → セキュリティ → シークレットマネージャー より、作成したシークレットにチェックを入れ、メンバーを追加します。
以下を設定し、保存します。
- 新しいメンバーに Functions を実行しているサービスアカウント を入力する
- ロールに Secret Manager のシークレットアクセサー を設定する
シークレット情報へのアクセスの確認
権限設定ができたので、再度確認してみましょう。
// 動作確認
$ curl https://asia-northeast1-YOUR_PROJECT_ID.cloudfunctions.net/helloWorld
Hello World : some secret value
できました!
まとめ
Secret Manager を利用して、Cloud Functions for Firebase からシークレット情報にアクセスする一例を書きました。
実際に運用する際には、まだまだ留意する点があるとは思いますが、Cloud KMS よりも簡単に扱えるのが感じられるかと思います。