Closed13

Secret Manager と Cloud Run, GitHub Actions, Next.js などを組み合わせるメモ

izuminizumin

前提:

  • Next.js アプリ
  • 本番は Cloud Run
  • CI/CD は GitHub Actions

この構成で、Secret Manager をどう使っていくか

izuminizumin

Secret Manager について

  • 1つの secret に1つの value
    • versioning は可能
    • 複数の key=value をグルーピングしたりする機能はない
izuminizumin

考えるのは主に3つのポイント

  • Secret の管理
    • パターン
      • 環境変数ごとに Secret 定義
      • dotenv みたいな全部入り Secret を定義し、.env の中身をいれる
  • docker build にどう渡すか(build-push-action 前提)
    • 前提: next build で解釈するので、.env ファイルもしくは環境変数が好ましい
    • パターン
      • .env のようなファイルに書き出しておいて、 secret-files でマウント
      • secrets に環境変数を列挙してマウント
      • build-args
  • 実行時の Cloud Run への展開
    • 提供されてる手段は2つ: ファイルとしてマウント, 環境変数にする
izuminizumin

ビルド時・実行時それぞれの制約や考慮ポイントによって、Secret 管理の方法が変わってくるはず

izuminizumin

ビルド時

  • buildx で特定レイヤでだけ secret をマウントする方法が提供されてるので、--build-arg はナシ
  • secrets で個別に secret を渡していくと、CI だけでなく Dockerfile にも環境変数リストが発生することになる
    • 「宣言的で大変よろしい」という考えるか、「同じリストを複数箇所で管理したくない」か
    • secret-files で渡して .env としてマウントするのが Docker 的には丸い
      • 「どうやって .env を生み出すか」という論点に変わる
izuminizumin

secret の取得は以下のような yaml を書くことになる

    - id: secrets
      uses: 'google-github-actions/get-secretmanager-secrets@v0'
      with:
        secrets: |-
          ENV_VAR1:<project>/<secret1>
          ENV_VAR2:<project>/<secret2>

これをどうやって .env にするか

izuminizumin

1 secret - 1 env-var の場合

    - run: |
        cat <<EOF > .env
        ENV_VAR1=${{ steps.secrets.outputs.ENV_VAR1 }}
        ENV_VAR2=${{ steps.secrets.outputs.ENV_VAR2 }}
        EOF

1 secret - N env-vars (secret に .env の中身がそのまま入ってる)場合

    - run: 
        cat <<EOF > .env
        ${{ steps.secrets.outputs.dotenv }}
        EOF

当たり前だけど、1つの secret に .env 突っ込む方式のほうが記述はラク
とはいえ個別の場合でも custom action を工夫すればそこまでひどいことにはならないか…?

izuminizumin

実行時

提供されてる手段は2つ: ファイルとしてマウント, 環境変数にする

だが、ファイルとしてマウントする場合、マウント先に指定したディレクトリはファイルが吹っ飛ぶ
(Docker の volume mount と同じと考えたらそりゃそうかという感じもする)
なので、 <rootDir>/.env にマウントできず、Next.js などが持つ .env 読む仕組みには乗れない
ENTRYPOINT とかでうまくやれなくもないが、気は乗らない…

izuminizumin

と、いうことで実行時は

  • 1 secret - 1 env-var にする
  • 環境変数として expose する

ほうが良いか?
ビルド時と同じく、リストの管理がどれくらい面倒になるか

izuminizumin

Secret 管理自体についても考える

Secret 管理パターン選定の観点

  • 環境変数ごとにつくる pros / cons
    • cons
      • project で namespace は1つなので、うまいこと prefixing する必要あり
      • 環境変数のリストを最低2箇所で管理しないといけない
        • 2箇所: build-push-action の secrets , gcloud run deploy の引数
        • それぞれビルド時, 実行時なので、入る値は別々
        • NEXT_PUBLIC_ ってランタイムでは不要なんだっけ?
    • pros
      • 実行時, ビルド時それぞれ何が必要なのかはコード(yaml)中に表現されることになる
  • Secert の naming
    • English letters, numbers, dashes, underscores しか使えない
izuminizumin

ビルド時・実行時ともに雑に .env としたとして、実際はビルド時と実行時で必要なものが違うという話もある
ビルド時に必要なもの => 配信する js に含まれる, 実行時に必要 => サーバで完結 なので、実行時のもののほうが秘匿性が高い傾向がある
なのでこの2つを一緒くたに扱うのは本来良くない

izuminizumin

アクセス権限について

  • 単純に secret manager accessor だけつけると範囲が広い
  • secret に都度権限設定はだるい
  • prefix で condition 設定できるので、それ使う?
    • label で絞り込みたいが、今はできない…?

いまはうまいこと prefix 考えて、CI 用と Cloud Run 用それぞれの service account に conditional な accessor 権限を付与するのが良さそう

izuminizumin

とりあえずの結論

以下のようにすると無難っぽい

  • Secret の設計
    • 1 secret - 1 env-var にする
    • prefix をつける: e.g. <app-name>-<ENV_VAR_NAME>
  • ビルド時
    • google-github-actions/get-secret-manager-secrets で、ビルド時に必要なものだけ取得
    • 取得した secret を cat 等で .env に書き出す
    • build-push-acitonsecret-file.env を渡す
  • 実行時
    • google-github-actions/deploy-cloudrunsecrets に、実行時に必要なものだけ列挙

ポートフォリオサイトに適用してみた
同じ環境変数リストが2箇所に出てくるビルド時変数取得の威圧感がデカい…

https://github.com/izumin5210/portfolio-webapp/pull/204

このスクラップは2022/03/19にクローズされました