5
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

はじめに

前回の記事では、Cloudflare Workersを使ったSlack Bot(技術記事下書きBot)の実装方法を紹介しました。その中で触れた署名検証について実装していきます。

なぜ署名検証が必要なのか

前回作成したBotのように、公開エンドポイントでSlackからのリクエストを受け取る場合、そのURLが第三者に知られると、偽のリクエストを送信される可能性があります。

このリスクを防ぐために、Slackの署名検証機能を実装する必要があります。

署名検証の仕組み

Slackの署名検証は以下の流れで動作します

  1. SlackがリクエストボディとタイムスタンプをHMAC-SHA256で署名
  2. 署名をリクエストヘッダー(x-slack-signature)に含めて送信
  3. Bot側で同じ方法で署名を計算し、一致するか確認

検証は以下の流れで行います

  1. (事前準備)Slack API設定画面から、Signing Secretを取得する
  2. リクエストからタイムスタンプを取得する
    リプレイ攻撃から保護するために、公式の例に則って検証時刻とタイムスタンプのずれが5分以上あれば以降の処理を中断します
  3. {バージョン番号}:{タイムスタンプ}:{リクエスト本文}の形式で連結する
  4. 署名シークレットをキーとして使用して連結文字列をハッシュし、ダイジェストを取得する
  5. 結果をリクエストヘッダーと比較する
    タイミング攻撃から保護するため、timingSafeEqualを利用して比較します

詳しくは公式ドキュメントを参照してください

実装

署名検証処理

import crypto from 'node:crypto';
import { Buffer } from 'node:buffer';

/**
 * Slackのリクエストを検証
 * @param headers リクエストヘッダー
 * @param body リクエストボディ
 * @param signingSecret Slackのシグネチャ
 * @returns リクエストが有効かどうか
 */
function isValidSlackRequest(headers: Headers, body: string, signingSecret: string): boolean {
	// 1. 必須ヘッダーの存在確認
	const timestamp = headers.get('x-slack-request-timestamp');
	const signature = headers.get('x-slack-signature');

	if (!timestamp || !signature) {
		console.warn('Missing required Slack headers');
		return false;
	}

	// 2. タイムスタンプ範囲検証(5分以内)
	const timestampNum = parseInt(timestamp, 10);
	const now = Math.floor(Date.now() / 1000);
	const timeDiff = Math.abs(now - timestampNum);
	if (timeDiff > 60 * 5) {
		console.warn('Request timestamp too old or future', { timeDiff });
		return false;
	}

	// 3. 署名形式の事前チェック
	if (!signature.startsWith('v0=') || signature.length !== 67) {
		// v0= + 64文字のhex
		console.warn('Invalid signature format');
		return false;
	}

	// 4. 署名検証
	const baseString = `v0:${timestamp}:${body}`;
	const computedSignature = 'v0=' + crypto.createHmac('sha256', signingSecret).update(baseString).digest('hex');

	// 5. タイミングセーフ比較
	try {
		return crypto.timingSafeEqual(Buffer.from(computedSignature), Buffer.from(signature));
	} catch (error) {
		console.warn('Signature comparison failed', error);
		return false;
	}
}

利用側

/**
 * handler
 */
export default {
	async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
		const rawBody = await request.text(); // 追加

		// 今回追加した署名検証
		if (!isValidSlackRequest(request.headers, rawBody, env.SLACK_SIGNING_SECRET)) {
			console.warn('Unauthorized – bad Slack signature', {
				timestamp: request.headers.get('x-slack-request-timestamp'),
				signature: request.headers.get('x-slack-signature'),
			});
			return new Response('Unauthorized – bad Slack signature', { status: 401 });
		}

		const url = new URL(request.url);

		if (request.method === 'POST' && url.pathname === '/slack/events') {
            // const rawBody = await request.text(); // 冒頭に移動
			const params = new URLSearchParams(rawBody);
			const responseUrl = params.get('response_url') ?? '';
			const text = params.get('text') ?? '';

			// 非同期バックグラウンド実行
			ctx.waitUntil(handleSlackEvent(text, responseUrl, env));
			// 即時レスポンス(Slackタイムアウト回避)
			return new Response(`✏️プロンプトを受け取りました。記事生成中です。\nprompt:\n${text}`);
		}

		return new Response('Not Found', { status: 404 });
	},
} satisfies ExportedHandler<Env>;

追加設定

wrangler.jsonc

	"compatibility_flags": ["nodejs_compat"],
	"compatibility_date": "2024-09-23", // 元の設定値を書き換える

Node.jsランタイムAPIのcryptoを使うため必要な設定です。
詳しくは公式ドキュメントを参照してください。

環境変数

型の追加

interface Env {
	OPENAI_API_KEY: string;
	GITHUB_TOKEN: string;
	GITHUB_REPO: string;
	GITHUB_BRANCH: string;
	SLACK_SIGNING_SECRET: string; // 追加
}

ターミナルで以下を実行し、環境変数を設定します

npx wrangler secret put SLACK_SIGNING_SECRET

入力欄表示後、SlackAPI設定画面より取得したSigning Secretを入力します。
設定後、Cloudflare Workersの設定画面から項目が確認できるようになっていればOKです。

デプロイ

前回記事同様、以下のコマンドでデプロイできます。

npx wrangler deploy

まとめ

前回記事に続き、SlackBotをCloudflareWorkers環境にデプロイする際必要な署名検証機能を追加しました。

これにより安全で実用的なBotになります。

参考

5
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?