Secret Manager と Cloud Run, GitHub Actions, Next.js などを組み合わせるメモ
前提:
- Next.js アプリ
- 本番は Cloud Run
- CI/CD は GitHub Actions
この構成で、Secret Manager をどう使っていくか
Secret Manager について
- 1つの secret に1つの value
- versioning は可能
- 複数の key=value をグルーピングしたりする機能はない
考えるのは主に3つのポイント
- Secret の管理
- パターン
- 環境変数ごとに Secret 定義
-
dotenv
みたいな全部入り Secret を定義し、.env
の中身をいれる
- パターン
- docker build にどう渡すか(build-push-action 前提)
- 前提:
next build
で解釈するので、.env
ファイルもしくは環境変数が好ましい - パターン
-
.env
のようなファイルに書き出しておいて、secret-files
でマウント -
secrets
に環境変数を列挙してマウント build-args
-
- 前提:
- 実行時の Cloud Run への展開
- 提供されてる手段は2つ: ファイルとしてマウント, 環境変数にする
ビルド時・実行時それぞれの制約や考慮ポイントによって、Secret 管理の方法が変わってくるはず
ビルド時
- buildx で特定レイヤでだけ secret をマウントする方法が提供されてるので、
--build-arg
はナシ -
secrets
で個別に secret を渡していくと、CI だけでなく Dockerfile にも環境変数リストが発生することになる- 「宣言的で大変よろしい」という考えるか、「同じリストを複数箇所で管理したくない」か
-
secret-files
で渡して.env
としてマウントするのが Docker 的には丸い- 「どうやって
.env
を生み出すか」という論点に変わる
- 「どうやって
secret の取得は以下のような yaml を書くことになる
- id: secrets
uses: 'google-github-actions/get-secretmanager-secrets@v0'
with:
secrets: |-
ENV_VAR1:<project>/<secret1>
ENV_VAR2:<project>/<secret2>
これをどうやって .env
にするか
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 を工夫すればそこまでひどいことにはならないか…?
実行時
提供されてる手段は2つ: ファイルとしてマウント, 環境変数にする
だが、ファイルとしてマウントする場合、マウント先に指定したディレクトリはファイルが吹っ飛ぶ
(Docker の volume mount と同じと考えたらそりゃそうかという感じもする)
なので、 <rootDir>/.env
にマウントできず、Next.js などが持つ .env
読む仕組みには乗れない
ENTRYPOINT
とかでうまくやれなくもないが、気は乗らない…
と、いうことで実行時は
- 1 secret - 1 env-var にする
- 環境変数として expose する
ほうが良いか?
ビルド時と同じく、リストの管理がどれくらい面倒になるか
Secret 管理自体についても考える
Secret 管理パターン選定の観点
- 環境変数ごとにつくる pros / cons
- cons
- project で namespace は1つなので、うまいこと prefixing する必要あり
- 環境変数のリストを最低2箇所で管理しないといけない
- 2箇所: build-push-action の
secrets
,gcloud run deploy
の引数 - それぞれビルド時, 実行時なので、入る値は別々
-
NEXT_PUBLIC_
ってランタイムでは不要なんだっけ?
- 2箇所: build-push-action の
- pros
- 実行時, ビルド時それぞれ何が必要なのかはコード(yaml)中に表現されることになる
- cons
- Secert の naming
- English letters, numbers, dashes, underscores しか使えない
ビルド時・実行時ともに雑に .env
としたとして、実際はビルド時と実行時で必要なものが違うという話もある
ビルド時に必要なもの => 配信する js に含まれる, 実行時に必要 => サーバで完結 なので、実行時のもののほうが秘匿性が高い傾向がある
なのでこの2つを一緒くたに扱うのは本来良くない
アクセス権限について
- 単純に secret manager accessor だけつけると範囲が広い
- secret に都度権限設定はだるい
- prefix で condition 設定できるので、それ使う?
- label で絞り込みたいが、今はできない…?
いまはうまいこと prefix 考えて、CI 用と Cloud Run 用それぞれの service account に conditional な accessor 権限を付与するのが良さそう
とりあえずの結論
以下のようにすると無難っぽい
- 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-aciton
にsecret-file
で.env
を渡す
-
- 実行時
-
google-github-actions/deploy-cloudrun
のsecrets
に、実行時に必要なものだけ列挙
-
ポートフォリオサイトに適用してみた
同じ環境変数リストが2箇所に出てくるビルド時変数取得の威圧感がデカい…