シークレットの扱い
Popcorn Web でのシークレットの規則はひとつです。プロセスには環境変数として届き、 コミットされるファイルやイメージにコピーされるファイルには決して書かれない。この ページの内容はすべてこの規則から導かれます。設定ファイルが「これはデプロイ側が 供給する」と言うための二つの書き方、手元のマシンが値を置くファイル、ホストごとの 注入のしかた、そして規則が破られたときに報告する検査です。
意図した例外がひとつあります。config.dev.toml には開発用データベースの DSN と、
pw init を実行したマシンのために生成された keyring シークレットが入っています。
このファイルはチームで共有するために書かれています。アプリケーションの隣で Devbox
が動かすデータベースのパスワードは資格情報ではなく fixture であり、pw doctor は
--env=dev ではこれについて黙っています。同じ内容が config.prod.toml にあれば
エラーです。
設定ファイルの二つの書き方
Section titled “設定ファイルの二つの書き方”スキャフォールドされた config.prod.toml は、データベース接続をこう書きます。
[[middleware.rdb.connections]]group = "default"dsn = "${DATABASE_URL}"${NAME} はファイルの読み込み時に、プロセスが起動したときの環境から展開されます。
未定義の名前は空の DSN ではなく読み込みエラーです。変数を渡し忘れたデプロイは、
どこにも繋がらないプールでリクエストを受け続けるかわりに、起動時に止まります。
参照形式はどのキーにも使えますし、テーブルの配列にはこれしかありません。繰り返される
テーブルには、フラットな環境変数名がないからです。
スカラーのキーにはそれぞれ環境変数もあります。名前から導かれるか、env:"NAME"
タグで指定されたものです。だから二つ目の書き方は、キーをファイルから外して変数を
設定することです。
SESSION_KEYRING_SECRET=$(openssl rand -base64 32)どちらの書き方も行き着く先は同じです。ファイルに「この値が存在する」と記録したい
ときは ${NAME} を選んでください。config.prod.toml を読む人が、そのファイルが
持たない入力も含めて、デプロイに必要な入力すべてを見渡せます。ファイルの中で誰も
探さないキー、たとえば OTLP のヘッダーなら、素の変数を選びます。各キーの変数は
設定リファレンスにあります。
自分の設定も同じ型に従います。secret:"mask" タグの付いたフィールドは起動時の
サマリと pw doctor からマスクされ、env タグがデプロイの設定する変数名を決めます。
type MailerConfig struct { APIKey string `secret:"mask" env:"MAILER_API_KEY" help:"transactional mail API key"`}タグがなくても、名前に secret、password、token、api_key、credential、
dsn といったよくある語を含むキーはマスクされます。smtp_pass や signing_seed
のようにその外にある名前はされないので、タグを付けてください。
手元のマシンで
Section titled “手元のマシンで”起動時に作業ディレクトリから、プロセスの環境変数より前に、4 つの dotenv ファイルが
この順で読まれます。.env、.env.local、.env.{APP_ENV}、.env.{APP_ENV}.local。
線が引かれているのは、コミットされる 2 つとされない 2 つの間です。.env と
.env.{APP_ENV} にはどのチェックアウトでも同じ値、たとえば共有のステージング
issuer の URL が入り、.local の 2 つは .gitignore が除外するものです。だから
シークレットはそこに置きます。
cp .env.example .env.local$EDITOR .env.local.env.example には、pw init で選んだ機能が開発以外の環境で読む変数が、値を空に
して並んでいます。ひとつの環境だけの値は .env.local ではなく .env.stg.local に
置いてください。同じチェックアウトをステージングとして動かすときと開発として
動かすときで、資格情報を共有しないためです。優先順位、APP_ENV 自体をどこに
書けるか、Cloudflare Workers のビルドが代わりに何を読むかといった仕組みの全体は
秘密情報と dotenv ファイルにあります。
ランタイムイメージが持つのはバイナリと config.prod.toml だけです。シェルも
パッケージマネージャもシークレットもありません。.dockerignore が config.dev.toml
と .local の dotenv ファイルすべてをビルドコンテキストから外すので、シークレットが
うっかりレイヤーにコピーされることはありません。イメージレイヤーは、後のレイヤーで
ファイルを消しても、pull できる誰もが読めるままだからです。
変数はプラットフォームから来ます。Compose の environment、ECS タスク定義の
valueFrom 付き secrets、Cloud Run の --set-secrets、env としてマウントした
Kubernetes の Secret。どれもプロセスの起動前に変数を設定します。ファイル層が
${NAME} を展開できるのはその瞬間だけです。
ファイルとしてマウントされたシークレットも読まれます。/run/secrets の下の通常
ファイルはそれぞれひとつの変数です。Docker が Compose や Swarm のシークレットを置く
場所であり、Kubernetes の Secret ボリュームも慣習的にここにマウントされます。
ファイル名が変数名、中身が値です。だから Compose ファイルは、環境変数のエントリなしで
データベース接続を渡せます。
services: app: secrets: [DATABASE_URL]secrets: DATABASE_URL: file: ./deploy/database_url.txtこのディレクトリは dotenv ファイルの後、プロセスの環境変数の前に読まれるので、
両方で設定された変数はプロセスの値になります。ここから読んだ値は、キーの名前が
何であれ起動時のサマリと pw doctor でマスクされます。マウントされているという
事実は、フィールド名よりも確かに「これはシークレットだ」と語るからです。.local
の dotenv ファイルから読んだ値もすべて同じ扱いです。スキャフォールドされた
Dockerfile が何をコピーし、それがなぜかは
コンテナイメージにあります。
Cloudflare Workers で
Section titled “Cloudflare Workers で”Worker にはファイルシステムがないので、TOML も dotenv ファイルも読まれません。
pw build --target cloudflare-workers は config.prod.toml のスカラーを、コンテナと
同じ変数名で wrangler.jsonc の vars に展開し、${NAME} 参照はその名前の
Wrangler シークレットから解決されます。
npx wrangler secret put DATABASE_URLターゲットの残りはサーバーレスにあります。
規則が守られたことを検査するもの
Section titled “規則が守られたことを検査するもの”pw doctor --env=prod はデプロイが読むのと同じようにプロジェクトを読み、値では
なくキーとファイルで報告します。
- 設定ファイルにリテラルとして書かれたシークレット(PW0412)と、そのファイルが git に 追跡されていること(PW0415)、所有者以外にも読めること(PW0416)
- スキャフォールドのプレースホルダのままの値(PW0413)。これはどの環境でも
--env=allで 2 つの環境ファイルに共有されているリテラル(PW0414)- コミットされる
.env.exampleに代入された値(PW0438)
.env.prod.local から読んだシークレットは設定ファイルのリテラルではないので
PW0412 は黙りますが、そのファイル自体は PW0415 と PW0416 の対象のままです。
起動時のサマリは実行時に同じことを語ります。マスクされたキーはすべて ***** と
出所のファイルか変数を示し、DSN はホストとデータベース名を残すので、このプロセスが
どのデータベースに繋がっているかはサマリから分かります。.local ファイルや
/run/secrets から来た値は出所だけを理由にマスクされるので、.env.local に書いた
ポートも ***** と出ます。共有する値を .env に置くべき理由がもうひとつ増えます。
シークレットではない値
Section titled “シークレットではない値”ポート、機能スイッチ、タイムアウト、ログレベルは TOML ファイルに置きます。デプロイを
読む人に見え、pw doctor が推論できる場所です。便利だからと設定をすべて
.env.local に移すと、その両方から設定が隠れます。--generate-config env の
ひな形を .env.example ではなく .env に書き出すのも、一手で同じことになります。
そこに並ぶ既定値はすべて、次の起動で誰かが設定した値として読まれます。
