Back to posts

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として別経路で渡す。これだけは徹底します。

漏れた「かもしれない」ときの初動

漏洩の確定を待っている時間はありません。「もしかしたら漏れたかも」の段階で動く前提で、手順を先に書いておきます。

  1. 発行元で失効させる。 何より先にこれです。使い回しを防ぐ意味でも、古いキーを無効化して新しいキーを発行する。「サイト側の設定を消す」だけでは、漏れた値そのものは生き続けてしまいます
  2. 新しい値をSecretとして登録し、再デプロイする。 PagesのSecretはデプロイのタイミングでバインドされるため、登録だけでは本番に反映されません。登録→デプロイの順を忘れないこと
  3. 漏れた経路を塞ぐ。 Gitの履歴に残ってしまった値は、たとえ後からファイルを消しても「公開されたもの」として扱う。だからこそ初動は履歴の掃除ではなく失効が先になります
  4. 影響範囲を見る。 そのキーで何にアクセスできるのか、発行元の利用ログに不審な形跡がないかを確認する

大事なのは順番で、原因究明や後始末より「その値を使えなくすること」が先です。普段から値の置き場所がSecretに寄っていれば、初動でやることもこの4手順に収まります。

このサイトでの決めごと

現時点のこのサイトは、秘密情報がほぼゼロの状態です。だからこそ、今のうちにこう決めておきます。新しいキーやトークンが増えたら、置き場所は原則Secret。本番とプレビューでキーを分け、ローカルの値は .dev.vars に置いてGitの外へ。PUBLIC_ は公開前提の値だけ。この4つを守る限り、「どこに書いたか分からないキー」が増えていく事態は避けられるはずです。

参考