Cloudflare Pages/Workersの環境変数と秘密情報の扱い:平文とSecretの使い分け
このサイトはCloudflare Pagesで公開している静的ブログで、正直なところ、今は秘密情報と呼べるものをほとんど持っていません。APIキーもトークンも、置く必要がないからです。
ただ、解析を入れたり、外部サービスと連携する機能を少し足したりするだけで、状況は変わります。「このキー、どこに書けばいいんだっけ」という場面が1つずつ増えていく。数が少ないうちは場当たりでも回りますが、増えてから置き場所を決め直すのは面倒です。まだ少ない今のうちにルールを決めておこう、というのがこの記事の出発点です。PagesとWorkersで公式に用意されている仕組みを確認しながら、自分のサイト向けの決めごととして整理しました。なお、このサイト自体は NetlifyからCloudflare Pagesへ移すときのチェックリスト に沿って移行したもので、環境変数の引き継ぎもそのときの確認項目の1つでした。
平文の環境変数とSecretは何が違うのか
PagesにもWorkersにも、値を渡す方法は大きく2種類あります。平文の「環境変数」と、暗号化される「Secret」です。コードから見るとどちらも env 経由で同じように読めるので混同しがちですが、保存と閲覧の扱いが決定的に違います。
| 項目 | 平文の環境変数 | Secret |
|---|---|---|
| 保存のされ方 | 平文のまま保存される | 暗号化されて保存される |
| 設定した後の表示 | ダッシュボードでいつでも値を確認できる | 一度設定すると、ダッシュボードでもWranglerでも値を再表示できない |
| コードからの読み方 | context.env.名前(Pages)/env.名前(Workers) | 環境変数とまったく同じ |
| 向いている値 | サイト名、公開前提のID、向き先を切り替えるフラグなど | APIキー、アクセストークン、パスワード類 |
| 設定方法 | ダッシュボードの「Variables and Secrets」、またはWrangler設定ファイル | ダッシュボードで Encrypt を選んで保存/Workersなら wrangler secret put |
ポイントは「コードからは区別がつかない」ことです。公式ドキュメントにも、Workerにとって環境変数とSecretに違いはなく、値がそのまま渡される、と明記されています。つまりSecret化の効果は実行時の挙動ではなく、「保存された値を人間が(そして漏れた画面やスクリーンショット経由で他人が)読めなくなる」ことにあります。逆に言えば、値をSecretに入れたからといって、ログに出力してしまえば意味がありません。そちらはコード側の作法の問題として別に扱います。
Pagesでの設定は、プロジェクトの Settings → Variables and Secrets で変数を追加し、秘密にしたいものはその場で Encrypt を選ぶだけです。Workersの場合はコマンドラインから登録します。
npx wrangler secret put API_TOKEN
# 実行すると値の入力を求められる。入力した値はそのまま新しいバージョンとしてデプロイされる
なおPagesでは、Secretは「そのSecretを使うデプロイより前に」設定しておく必要があります。登録しただけでは動いているデプロイには反映されず、次にデプロイした時点から有効になります。後述する「漏れたときの初動」で効いてくるので、ここは覚えておく価値があります。
ProductionとPreviewは別の金庫として扱う
Pagesの環境変数とSecretは、Production(本番)とPreview(プレビュー)で別々に設定できます。ビルド時にも実行時にも使えるのですが、この分離は「便利だから」ではなく「分けないと危ないから」使うものだと考えています。
原則:プレビュー環境に本番のキーを入れない。 プレビューは確認のための場所で、URLが共有されることも、誰でもアクセスできる状態になることもある。本番キーで外部サービスを叩けば、検証のつもりが本番データや課金に触れてしまう。
プレビュー側には、検証用のキーや、そもそも外部連携を無効化するフラグを入れます。このサイトで言えば、解析のように「本番だけで動いてほしいもの」は、プレビュー側で値を空にするか、送信先を変えるのが安全です。分離の手間は初回だけなので、後から慌てるより先に分けておく方が安くつきます。プレビュー環境そのものの運用については、Cloudflare Pagesのプレビューデプロイ運用メモで別に整理しています。
ローカル開発は.dev.vars、ただしGitの外に置く
手元の開発環境でFunctionsやWorkersを動かすときは、Wrangler設定ファイルと同じ階層に .dev.vars ファイルを置き、dotenv形式で書くのが公式のやり方です。
# .dev.vars(このファイル自体はリポジトリに含めない)
API_TOKEN="ローカル確認用のダミー値"
守るべきは中身より置き場所で、ルールは単純です。
.dev.varsと.envは必ず.gitignoreに入れる- すでにGitの追跡対象になってしまったファイルは、
.gitignoreに足すだけでは不十分で、git rm --cachedで追跡を外す必要がある - リポジトリに残してよいのは、値が空かダミーの
.dev.vars.exampleのような雛形だけ
もう1つ、環境別ファイルの挙動には注意点があります。.dev.vars.<環境名> というファイルを作ると、その環境を選んだときは そのファイルだけ が読み込まれ、共通の .dev.vars は読み込まれません。必要なSecretは環境別ファイル側にも全部書く必要があります(.env 系ファイルは複数がマージされるので、挙動が逆です)。この違いを知らずに「共通ファイルに書いたのに読まれない」で時間を溶かすのは、もったいない。
このサイトで一番危ないのはPUBLIC_始まりの変数
このサイトはAstroで作っているので、環境変数の話はフレームワーク側の仕様とも絡みます。Astro(と、その土台のVite)では、クライアント側のコードから読めるのは PUBLIC_ で始まる名前の変数だけです。裏を返せば、PUBLIC_ を付けた値はブラウザに渡るJavaScriptに埋め込まれ、誰でも読める状態で配信されます。サーバー側だけで使う値は PUBLIC_ を付けずに読む、というのが前提の設計です。
線引き:
PUBLIC_に入れてよいのは「ブラウザの開発者ツールで見られても困らない値」だけ。 計測用の公開IDのように、性質として公開が前提の値に限る。
落とし穴は、Secretに登録したかどうかと、この話は別だという点です。値の置き場所をCloudflare側で暗号化しても、コード側で PUBLIC_ 付きの名前で参照してクライアントに渡してしまえば、秘密ではなくなります。「暗号化して保存する」と「クライアントに渡さない」は別の防衛線で、両方が必要です。
やってはいけないこと(確認リスト)
増やす前に決めたルールを、チェックリストの形にしておきます。新しいキーを追加するたびに、この5つだけ確認する運用にします。
- APIキーやトークンを、リポジトリにコミットされるファイル(Wrangler設定ファイルの
varsを含む)へ直書きしていないか -
PUBLIC_で始まる名前に、秘密の値を入れていないか - プレビュー環境に、本番のキーをコピーしていないか
-
.dev.varsや.envが.gitignoreに入っていて、Gitの追跡対象からも外れているか - ログ出力やエラーメッセージに、Secretの値そのものを出していないか
とくに1つ目は、Wrangler設定ファイルに平文の変数を書く場所(vars)があるぶん紛らわしいところです。設定ファイルはリポジトリに含めるのが前提なので、そこに書いた時点で「平文で公開リポジトリに載せた」のと同義になります。秘密は設定ファイルではなく、Secretとして別経路で渡す。これだけは徹底します。
漏れた「かもしれない」ときの初動
漏洩の確定を待っている時間はありません。「もしかしたら漏れたかも」の段階で動く前提で、手順を先に書いておきます。
- 発行元で失効させる。 何より先にこれです。使い回しを防ぐ意味でも、古いキーを無効化して新しいキーを発行する。「サイト側の設定を消す」だけでは、漏れた値そのものは生き続けてしまいます
- 新しい値をSecretとして登録し、再デプロイする。 PagesのSecretはデプロイのタイミングでバインドされるため、登録だけでは本番に反映されません。登録→デプロイの順を忘れないこと
- 漏れた経路を塞ぐ。 Gitの履歴に残ってしまった値は、たとえ後からファイルを消しても「公開されたもの」として扱う。だからこそ初動は履歴の掃除ではなく失効が先になります
- 影響範囲を見る。 そのキーで何にアクセスできるのか、発行元の利用ログに不審な形跡がないかを確認する
大事なのは順番で、原因究明や後始末より「その値を使えなくすること」が先です。普段から値の置き場所がSecretに寄っていれば、初動でやることもこの4手順に収まります。
このサイトでの決めごと
現時点のこのサイトは、秘密情報がほぼゼロの状態です。だからこそ、今のうちにこう決めておきます。新しいキーやトークンが増えたら、置き場所は原則Secret。本番とプレビューでキーを分け、ローカルの値は .dev.vars に置いてGitの外へ。PUBLIC_ は公開前提の値だけ。この4つを守る限り、「どこに書いたか分からないキー」が増えていく事態は避けられるはずです。
参考
- Cloudflare Pages ドキュメント「Bindings(Environment variables/Secrets)」: https://developers.cloudflare.com/pages/functions/bindings/
- Cloudflare Workers ドキュメント「Secrets」: https://developers.cloudflare.com/workers/configuration/secrets/
- Wrangler コマンドリファレンス「pages secret」: https://developers.cloudflare.com/workers/wrangler/commands/pages/
- Astro ドキュメント「Using environment variables」: https://docs.astro.build/en/guides/environment-variables/