zudo-slack-wisdom
GitHub リポジトリ

検索したい単語を入力

いつでも検索バーを開ける

シークレットと設定

SLACK_BOT_TOKEN と SLACK_SIGNING_SECRET を Worker のシークレットとして設定する。vars にもクライアントバンドルにも載せない。

概要

Slack 連携に必要な秘密の値は、ちょうど 2 つである。Web API を呼ぶための bot トークン(SLACK_BOT_TOKENxoxb-…)と、受信リクエストを検証するための signing secret(SLACK_SIGNING_SECRET)だ(リクエストの検証を参照)。どちらも wrangler secret put で設定する Worker のシークレットとして扱う。wrangler.tomlvars に置いてはいけないし、バンドラーがクライアント向け JS に取り込みうる場所から参照してもいけない。

wrangler secret put

npx wrangler secret put SLACK_BOT_TOKEN
npx wrangler secret put SLACK_SIGNING_SECRET

各コマンドは値の入力を促し、それをそのままデプロイ済みの Worker へ送る。wrangler.toml にもリポジトリ内のどのファイルにも書き込まれることはない。Cloudflare のシークレットに関するドキュメントによれば、wrangler secret put は新しい Worker のバージョンを作って即座にデプロイする。段階的なロールアウトをしたい場合は、デプロイせずにバージョンだけを作る wrangler versions secret put を使う。

Slack のトークンや signing secret を vars に入れてはいけない

wrangler.toml[vars] ブロックは平文で、リポジトリにコミットされ、Cloudflare のダッシュボードからも見える。Cloudflare 自身のガイダンスも明確だ。曰く、「Worker の Wrangler 設定ファイルで機微情報を保存するのに vars を使ってはいけない。代わりにシークレットを使うこと」SLACK_BOT_TOKENSLACK_SIGNING_SECRET は、まさにこのルールが想定している類の値である。

ローカル開発: .dev.vars

wrangler dev は、wrangler.toml と同じ階層に置いた .dev.vars ファイルから、ローカル限定のシークレットを dotenv 形式で読み込む。

SLACK_BOT_TOKEN="xoxb-your-dev-workspace-token"
SLACK_SIGNING_SECRET="your-dev-workspace-signing-secret"
# .gitignore
.dev.vars*

.dev.vars(使っているなら .env も)は決してコミットしないこと。ローカル開発を楽にしてくれるこのファイルは、git の履歴に入った瞬間に平文のシークレットになる。Cloudflare は環境ごとのローカル上書き用に .dev.vars.<environment-name> もサポートしており、こちらは汎用の .dev.vars より先に読み込まれる。上のグロブはこうした派生形もカバーするが、.dev.vars とだけ書いた行ではカバーできない。

型付きの Env

正しく型付けされた Env をどちらの経路で生成するかは、導入している Wrangler のバージョンによって変わる。

推奨: 設定に必須シークレットを宣言してから wrangler types

Wrangler の secrets 設定プロパティを使うと、必須シークレットの名前だけ(値は決して含めない)を wrangler.toml に直接宣言できる。

[secrets]
required = [ "SLACK_BOT_TOKEN", "SLACK_SIGNING_SECRET" ]

これを設定しておくと、wrangler types.dev.vars から名前を推測するのではなく secrets.required からタイプ付きバインディングを生成するようになる。そのため、.dev.vars が存在しない CI などの環境でも型生成が動くようになり、生成結果は worker-configuration.d.ts に書き込まれる。これを設定した後は、生成されたこのファイルを Env の唯一の情報源として扱うこと。下記のインターフェイスを手書きで併用すると、両者が食い違っていく。secrets は実行時の検証も追加する。wrangler dev は必須シークレットが .dev.vars に見当たらないと警告し、wrangler deploy / wrangler versions upload は必須シークレットが実際に Worker に設定されていなければデプロイを拒否する。(出典: Cloudflare の Wrangler 設定リファレンスおよびsecrets-config-property の changelog エントリ。)

ここで併せて知っておく価値のある、別系統だが関連するフラグがある。wrangler types --strict-vars(既定は true)は vars の値に対してリテラル/ユニオン型を生成する。vars の値が環境ごとに正当に異なり、そのリテラルユニオン型が邪魔になる場合は --strict-vars=false を渡す。このフラグは vars の型付けにしか関与せず、secrets.required には影響しない。

フォールバック: 手書きのインターフェイス(古い Wrangler の場合)

secrets 設定プロパティがない場合、シークレットは Wrangler から見える形で宣言されることが一切ない(宣言されるのは vars とバインディングだけだ)。代わりに Env のフィールドを手で書き、env["SLACK_BOT_TOKEN"] のような文字列頼みの参照ではなく、すべてのハンドラーでコンパイル時のチェックを効かせるようにする。

export interface Env {
  SLACK_BOT_TOKEN: string;
  SLACK_SIGNING_SECRET: string;
  // ... other bindings: KV, D1, etc.
}

export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
    // env.SLACK_BOT_TOKEN and env.SLACK_SIGNING_SECRET are typed strings here
    return await handleRequest(request, env, ctx);
  },
} satisfies ExportedHandler<Env>;

実際のアプリが最終的に必要とするもの

SLACK_BOT_TOKENSLACK_SIGNING_SECRET は、Web API を呼び出しリクエストを検証するための最小構成をカバーするに過ぎない。本番の連携における Env の表面積は、この 2 つをはるかに超えて膨らんでいく。

  • チャンネル ID。 読み取り元のソースチャンネル、投稿先のデスティネーションチャンネル、そして連携がスコープしているそのほかのチャンネル。これらはシークレットではないが、ワークスペースごとに異なるので、ハードコードではなく設定に置くべきものだ。

  • bot 自身のユーザー ID(SLACK_BOT_USER_ID)。 これがないと、bot 自身も投稿しているチャンネルの動きに反応する Events API ハンドラーが、自分自身の出力にループしてしまうことがある。自分のメッセージやリアクションのイベントを見て、それに反応してしまうのだ。bot のユーザー ID(または bot_id)でフィルタリングするには、その ID がハードコードでも毎リクエストの検索でもなく、設定として利用可能になっている必要がある。

vars は文字列である -- 真偽値らしきフラグは明示的な比較が必要

vars の値は、wrangler.toml の見た目がどうであれ、すべて文字列としてやってくる。機能を切り替えるためのフラグは、真偽判定ではなく、リテラルの文字列と比較しなければならない -- env.FEATURE_FLAG === "true" のように。"false" は空でない文字列なので truthy である。真偽判定を使うと、まさにそのフラグが無効化しようとしていた挙動を、黙って再び有効にしてしまう。

ローカル上書きの優先順位。 wrangler dev --var NAME:value による CLI 上書きは .dev.vars に勝ち、.dev.vars は設定ファイルの vars の既定値に勝つ。これにより、デプロイ済みの Worker が構造的に到達できない -- デプロイ時に --var フラグは存在しない -- フォールバック値を、ローカル開発だけがオプトインできるようになる。ローカル限定の抜け道として便利ではあるが、あるデバッグセッションのために使った --var が、いつの間にか「ローカルではいつもこうやって動かすもの」に化けてしまわないよう、この優先順位は把握しておく価値がある。

シークレットが欠けているときは fail closed にする。 wrangler secret put を実行し忘れたせいで env.SLACK_BOT_TOKEN を読んで undefined を得たハンドラーは、欠けているシークレット名と、それを直すための正確なコマンド(wrangler secret put SLACK_BOT_TOKEN)を明示した説明的な 5xx を返すべきであって、黙って何もしなかったり、何らかのデフォルトトークンにフォールバックしたりしてはいけない。コミットされたデフォルト値が許容されるのは、明示的でローカル開発限定の var の背後にゲートされている場合だけであり、本番でも発動しうる無条件のフォールバックとしては決して許容されない。

漏洩したトークン、期限切れのトークンを差し替える

wrangler secret put SLACK_BOT_TOKEN をもう一度実行すれば古い値は上書きされ、即座にデプロイされる。Worker 側に「失効」という別ステップは存在しない。まず Slack のアプリ管理コンソールでトークンをローテーションし(漏洩が確定しているなら差し替え直後でもよい)、同じコマンドで新しい値を push する。

プレビューデプロイは本番のすべてのバインディングを共有する

CI が明示的に、独自のバインディングを持つ別の Worker 環境へプレビュービルドをデプロイしているのでないかぎり、同じ Worker の未昇格のプレビューバージョンは、本番のあらゆるバインディングを共有する -- シークレットだけでなく、D1、KV、そして bot トークンが指す実際の Slack チャンネルまでもがそうだ。プレビューから叩いたエンドポイントには、本番への副作用がある。ある本番のリファレンス連携では、プレビュービルドの管理用エンドポイントが本番チャンネルにメッセージを配信してしまったことがあった。プレビューの Worker が、本番の SLACK_BOT_TOKEN で本番のチャンネル ID に対して Web API を呼び出していたからだ。「プレビュー」という言葉は、そのどちらのスコープも変えてはくれない。

プレビューは本番の D1 データベースも共有しているため、スキーマ変更は追記のみに保たなければならない。本番がまだ読んでいるカラムを削除・改名すると、(本番より少し古い、あるいは少し新しいコードで動いているかもしれない)プレビューバージョンが同じテーブルに触れた瞬間に壊れうる。追記のみのルールの全体、それを強制する CI ガード、そしてどうしても破壊的にならざるをえないマイグレーション向けの脱出口ラベルについてはプレビューデプロイと D1 マイグレーションを参照。

独自のシークレットとバインディングを持つ別環境が実際に用意されているのでないかぎり、PR のプレビューが隔離されたワークスペースだと思い込まないこと。

つまずきどころ

  • vars とシークレットはコードから見ると区別がつかない -- どちらでも env.SLACK_BOT_TOKEN と書く。違いはその値がどうやってそこに入ったかだけにある。コードは警告してくれないので、宣言の仕方を最初から正しくしておくこと。

  • .dev.vars の値は本番には届かない -- あくまで wrangler dev のためだけに存在する。.dev.vars に新しいシークレットを足したあと wrangler secret put を忘れる、というのが「ローカルでは動くのに本番で 500」の典型パターンである。

  • ダッシュボードで設定したシークレットと CLI で設定したシークレットは、動作としてはまったく同じ -- ただしプロジェクトごとに、どちらか一方だけを信頼できる情報源として扱うべきだ。そうしないと、環境をまたいで両者が静かに食い違っていく。

  • wrangler secret put は即座にデプロイされるが、vars の変更は再デプロイが要る。 新しいシークレットを push すると新しい Worker のバージョンが作られてすぐに有効になるが、wrangler.tomlvars の値を変えても、次の wrangler deploy が走るまでは反映されない。この 2 つを混同すると、「var を更新したのに何も変わらない」という混乱がよく起きる。

Revision History

作成更新