LoginSignup
1
1

More than 3 years have passed since last update.

Cloud Functions for Firebase + Cloud KMS でシークレット情報を扱う

Last updated at Posted at 2020-12-09

開発時に後回しになりがちなシークレット情報を Cloud Functions for Firebase + Cloud KMS を利用して取り扱う方法を記載します。

--
(2020/12/10追記)
別途 Cloud Functions for Firebase + Secret Manager でシークレット情報を扱う という記事を書きました!
現在は Secret Manager を使った方が主流だと思いますので、合わせてご覧ください。

はじめに

サービスを開発し始めると、外部サービスのシークレットキーや認証用のID/PASSWORDなど、秘匿しなければいけない情報を扱いたいケースが多々あります。
これらの情報はソース管理するべきではない情報であるため、環境変数等を利用して対応するケースも多いかと思います。
Cloud Functions for Firebase にも 環境変数 はありますが、シークレット情報を直接扱うことには適していません。
Cloud Functionsのドキュメント にも以下のように記載があります。

シークレットの管理
環境変数は関数の構成に使用できますが、データベースの認証情報や API キーなどの機密情報の格納には適しません。 このような機密性の高い値は、ソースコードや外部の環境変数以外の場所に保存する必要があります。一部の実行環境やフレームワークでは、環境変数の内容がログに送信されることがあります。YAML ファイル、デプロイ スクリプト、ソース管理に重要な認証情報は保存しないでください。

シークレットを保存する場合は、シークレット管理のベスト プラクティスを確認することをおすすめします。Cloud KMS と Cloud Functions の固有の統合はありません。

この記事では、Cloud KMS を使ってシークレット情報の暗号化/復号化を行う方法を記載します。

Cloud Key Management Service (Cloud KMS) について

Cloud KMS とは、GCPの暗号鍵管理サービスです。
詳しく知りたい方は詳細は公式ドキュメントをご覧ください。

対象読者

  • Cloud Functions for Firebase を利用したことがある方
  • GCP の扱いに少しでも慣れている方
    GCP コンソール の細かい操作方法は省略している箇所がありますので、GCPを触ったことない方は分かりにくいかもしれません。

大まかなステップ

  1. Cloud KMS の設定
  2. Cloud KMS で暗号化する
  3. 暗号化した情報を Cloud Functions for Firebase の環境変数に登録する
  4. Functions で暗号化された環境変数を復号化する

Cloud KMS の設定

実践してみたい方はGCPの新規プロジェクトを作成して試してみることをお勧めします。

API の有効化

GCP コンソール → APIとサービス より、Cloud Key Management Service (KMS) API を有効化します。
APIの有効化

キーリングの作成

GCP コンソール → セキュリティ → 暗号鍵 より、キーリングを作成します。
セキュリティ - 暗号鍵

「キーリングの名前」を入力し、作成します。
キーリングの作成

「鍵名」を入力し、作成します。
鍵を作成

「ステータス」が 利用可能 になっていれば作成終了です。
ステータス

Cloud KMS で暗号化する

gcloud sdkを使って暗号化します。
事前にgcloudの対象プロジェクトを切り替えておいてください。
先ほど作成した キーリング/鍵 を使って暗号化します。

// 暗号化したいワード → some secret key とします。
$ echo -n "some secret key" | gcloud kms encrypt \
   --location=asia-northeast1  \
   --keyring=hoge-ring \
   --key=hoge-key \
   --plaintext-file=- \
   --ciphertext-file=- | base64

CiQAkW+QvrLG2eA8WdcjdnA5LgND0wAdh3YDwnN4jkNaRjDz3RkSNwDx02zmdnf9DDGODE9tC4bkUg0M28wkMFY6LSru/Z+uMi/tavKCii1dzmbPKQV5ZsEIdaG8hSQ=

Cloud Functions for Firebase で復号化処理の作成

Functions 用の環境がない方は、firebase init 等で事前に環境を生成してください。
Cloud KMS を使うのに必要なモジュールをインポートします。

npm install @google-cloud/kms

Cloud KMSの情報と先ほど暗号化したものを環境変数に設定します。

$ firebase functions:config:set project.id="YOUR_PROJECT_ID"
$ firebase functions:config:set kms.keyring="hoge-ring"
$ firebase functions:config:set kms.key="hoge-key"
$ firebase functions:config:set kms.location="asia-northeast1"
$ firebase functions:config:set secret.encryptedData="CiQAkW+QvrLG2eA8WdcjdnA5LgND0wAdh3YDwnN4jkNaRjDz3RkSNwDx02zmdnf9DDGODE9tC4bkUg0M28wkMFY6LSru/Z+uMi/tavKCii1dzmbPKQV5ZsEIdaG8hSQ="

念のため設定した内容を確認します。

$ firebase functions:config:get
{
  "kms": {
    "keyring": "hoge-ring",
    "key": "hoge-key",
    "location": "asia-northeast1"
  },
  "secret": {
    "encryptedData": "CiQAkW+QvrLG2eA8WdcjdnA5LgND0wAdh3YDwnN4jkNaRjDz3RkSNwDx02zmdnf9DDGODE9tC4bkUg0M28wkMFY6LSru/Z+uMi/tavKCii1dzmbPKQV5ZsEIdaG8hSQ="
  },
  "project": {
    "id": "YOUR_PROJECT_ID"
  }

実際の処理を書いていきましょう。
今回は HTTP トリガーとします。

import * as functions from 'firebase-functions';
import { KeyManagementServiceClient } from '@google-cloud/kms';

export const helloWorld = functions
    .region("asia-northeast1")
    .https
    .onRequest(async (request, response) => {
        // プロジェクトの情報を環境変数から取得します。
        const projectId = functions.config().project.id;

        // 暗号化されたデータを環境変数から取得します。
        const encryptedData = functions.config().secret.encryptedData;

        // KMS の情報を環境変数から取得します。
        const keyring = functions.config().kms.keyring;
        const key = functions.config().kms.key;
        const location = functions.config().kms.location;

        // 復号化します。
        const client = new KeyManagementServiceClient();
        const keyName = client.cryptoKeyPath(projectId, location, keyring, key);
        const [result] = await client.decrypt({name: keyName, ciphertext: encryptedData,});
        const decryptedData = result.plaintext?.toString();

        // decryptedData を出力するのはセキュアではないですが、今回は検証のため出力しています。
        response.send(`Hello World : ${decryptedData}`);
    });

ではデプロイして動作を見てみましょう。

// デプロイ
$ firebase deploy --only functions
...

// 動作確認
// ******* はプロジェクトID
$ curl https://asia-northeast1-*******.cloudfunctions.net/helloWorld
Error: could not handle the request

何やらエラーがでました。
Functions のログをみてみると以下のようにエラーが出ているのが確認できます。

// ******* はプロジェクトID
PERMISSION_DENIED: Permission 'cloudkms.cryptoKeyVersions.useToDecrypt' denied on resource 'projects/*******/locations/asia-northeast1/keyRings/hoge-ring/cryptoKeys/hoge-key'

Functions から KMS にアクセスするには、権限が必要なようです。
Functions を動かしているサービスアカウントに復号化の権限を設定します。

権限の確認と設定

Functions を実行しているサービスアカウントの確認

GCPコンソール → Cloud Functions → helloWorld → 詳細タブ を開きます。
サービスアカウントが記載されています。
YOUR_PROJECT_ID@appspot.gserviceaccount.com というサービスアカウントで実行されていることがわかります。
起動サービスアカウントの確認

権限の設定

GCP コンソール → IAMと管理 を開きます。
先ほどの YOUR_PROJECT_ID@appspot.gserviceaccount.com というメンバーが存在するので編集ボタンを押します。
新しいロールとして、Cloud KMS → クラウド KMS 暗号鍵の復号化 を追加します。
ロールの追加

復号化の確認

権限の設定ができたので、復号化の確認をします。

// ******* はプロジェクトID
$ curl https://asia-northeast1-*******.cloudfunctions.net/helloWorld
Hello World : some secret key

できました!

まとめ

  • シークレット情報を利用する際は Cloud KMS を利用して暗号化/復号化を行う。
  • Functions から Cloud KMS にアクセスする際は権限をつける。
    今回は Functions 内で復号化しかしていませんが、暗号化する際は暗号化の権限が必要です。
  • シークレットでない環境情報は firebase functions:config:set で設定する。

補足

Cloud Functions を使えば特定の関数のみ復号化の権限をつけることもできます。
Cloud Functions にはサービスアカウントの設定がありますので、復号化権限を持つサービスアカウントを別途作成し、該当の関数にサービスアカウントを指定することで特定の関数のみ Cloud KMS にアクセスできるようになります。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1