🔑

VercelにデプロイしたアプリからSecret Managerを利用する

2023/07/23に公開

こんにちは。
自作アプリを作っている際に、NotionのAPIKeyやDBIDをGoogle CloudのSecret Managerに保存したいけど、Vercelにデプロイしているアプリからだとどうすればいいのだろう?となったのでまとめます。
ベストプラクティスは、おそらくWorkload Identity連携を用いるのがベターのようですが、個人開発のレベルだったのでサービスアカウントを利用するやり方を採用しています。

この記事のゴール

VercelにデプロイしているNext.jsのアプリのAPI RoutesからGoogle CloudのSecret Managerにアクセス行い、APIKeyなどを保存したり取得したりできるようにする。

手順

(この記事に辿り着いている時点である程度、今回やりたいことに関して理解されているだろうと思い色々省いて説明しています)

  1. Secret Managerにアクセス権があるGoogle Cloudのサービスアカウントを用意する
  2. Secret ManagerのSDKをインストール
  3. 必要な環境変数をVercelに保存しておく
  4. 環境変数とSDKを利用してSecret Managerを叩く

1. サービスアカウントの用意

公式ドキュメントなどを参考にしてください🙇

2. SDKをインストール

公式を参考にして入れます。私はyarnを利用しているので以下のようなコマンドで入れました。

yarn add @google-cloud/secret-manager

3. 必要な環境変数をVercelに保存しておく

今回は以下の3つを利用します。
全てサービスアカウントのJSONキーから取得しています。

  • PRIVATE_KEY
  • CLIENT_EMAIL
  • PROJECT_ID

私はそのままの名前で3つを保存しています。

今回のコードはクライアント側で実行しないことを前提にしています。そのため、サービスアカウントのJSONからPRIVATE_KEYを取得しそのまま環境変数としてVercelに保存しています。

4. 環境変数を利用してSecret Managerを叩く

以下のコードはAPI Routes上で実行しています。

以下のようなコードを用意して使いまわしています。
ポイントは2つです。

  1. コードを見てもらうとわかると思いますが、PRIVATE_KEYをbase64エンコードしています
  2. SecretManagerServiceClientを初期化する際にauth変数を渡しており、そこであらかじめ作成したauth変数を利用することでデフォルトのサービスアカウントなどを探しに行かないようにしています。
let client: SecretManagerServiceClient | null = null

export const getClient = () => {
  if (client === null) {
    const privateKey = process.env.PRIVATE_KEY ?? ''
    const private_key = Buffer.from(privateKey, 'base64').toString('utf8')

    // 一回GoogleAuth型のauthを作るとサジェストが効いて楽
    const auth = new GoogleAuth({
      credentials: {
        private_key: private_key.replace(/\\n/g, '\n'),
        client_email: process.env.CLIENT_EMAIL,
      },
      projectId: process.env.PROJECT_ID,
    })

    // authを渡すことでデフォルトのサービスアカウントを探しに行かなくなる
    client = new SecretManagerServiceClient({ auth })
  }

  return client
}

まとめ

以上の4ステップでおそらくVercelにデプロイしたアプリからでもSecret Managerを利用できるようになっていると思います。
今回の記事はサービスアカウントを利用してGoogle Cloudのサービスを利用する際には応用が効くと思う(SDKのクライアントの初期化がポイント)のでよければ他のサービスを利用する際にも参考にしてみてください。
個人的にはGoogleAuth型でauth変数を初期化するところがポイントで、うまく行っていない頃は間違えてprivate_key, client_email, project_idを全て同じレベルにしてobjectを渡していたりしたので、その辺で詰まっている人に届くと嬉しいです。

NGコード(参考)

const client = new SecretManagerServiceClient({
      private_key: privateKey.replace(/\\n/g, '\n'),
      client_email: process.env.CLIENT_EMAIL,
      projectId: process.env.PROJECT_ID,
    })

Discussion