0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

AWS LambdaでGitHub Webhooksのシークレットトークンを検証する

Last updated at Posted at 2023-05-04

GitHub Webhooksはさまざまなイベントを検知してペイロードを送信できるので、とても便利です。
ただし、セキュリティ対策をしないと意図しないリクエストを処理してしまうかもしれません。

GitHub Webhooksでは、シークレットトークンによる検証の仕組みが用意されており、GitHubからのリクエストに制限することがお勧めされています。
image.png

今回は、AWS Lamndaで作成したWebhookでシークレットトークンを検証する方法を紹介します。

シークレットトークンを検証するコード

検証するコードの全体は以下です。
TypeScriptで記載しているため、トランスパイルが必要です。

  • ランタイム: Node.js 18.x
  • ハンドラ: index.handler
  • トリガー: API Gateway (Lambdaプロキシ統合)
  • 環境変数: GitHubWebhookSecretにシークレットトークン値を設定
index.ts
import {
  APIGatewayProxyEvent,
  APIGatewayProxyEventHeaders,
  APIGatewayProxyResult,
} from 'aws-lambda';
import * as crypto from 'crypto';

/**
 * リクエストのシークレットトークンを検証
 */
export const isValidSignature = (
  body: string | null = '',
  headers: APIGatewayProxyEventHeaders = {}
): boolean => {
  const sigHeaderName = 'X-Hub-Signature-256';
  const sigHashAlg = 'sha256';
  const secret = process.env.GitHubWebhookSecret ?? '';

  if (!headers.hasOwnProperty(sigHeaderName)) {
    return false;
  }

  try {
    const sig = Buffer.from(headers[sigHeaderName] || '', 'utf8');
    const data = body ?? '';
    const hmac = crypto.createHmac(sigHashAlg, secret);
    hmac.update(data);
    const digest = Buffer.from(`${sigHashAlg}=${hmac.digest('hex')}`, 'utf8');

    return crypto.timingSafeEqual(digest, sig);
  } catch (err) {
    console.error(err);
    return false;
  }
};

/**
 * Lambda関数のハンドラ
 */
export const handler = (event: APIGatewayProxyEvent): APIGatewayProxyResult => {
  if (!isValidSignature(event.body, event.headers)) {
    return {
      statusCode: 500,
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        message: 'invalid',
      }),
    };
  }

  return {
    statusCode: 200,
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      message: 'ok',
    }),
  };
};

解説

検証方法

ペイロードを検証する方法は公式ドキュメントに記載されています。

要点をまとめると以下です。

  • シークレットトークンは x-hub-signature-256 としてリクエストヘッダーに含まれる
  • シークレットトークンの値と、ペイロード本文を使用して、HMAC hexダイジェストのハッシュ値となる
  • == 演算子ではなく、定数時間の文字列比較をすることが推奨

また、実際に送信されたWebhookのリクエストは、GitHub上から
[Settings]タブ → [Webhooks] → 作成したWebHooksの[Edit] → Recent Deliveries
で、確認できます。
image.png

検証するコード

以上をふまえてコードの解説をします。

  • X-Hub-Signature-256 が存在するか判定します
// const sigHeaderName = 'X-Hub-Signature-256';
if (!headers.hasOwnProperty(sigHeaderName)) {
  return false;
}
  • リクエストヘッダーから X-Hub-Signature-256をBufferで取得します
// const sigHeaderName = 'X-Hub-Signature-256';
const sig = Buffer.from(headers[sigHeaderName] || '', 'utf8');
  • シークレットトークンとペイロード本文(リクエストbody)から、sha256でHMACを作成します
// const sigHashAlg = 'sha256';
// const secret = process.env.GitHubWebhookSecret;
const data = body ?? '';
const hmac = crypto.createHmac(sigHashAlg, secret);
hmac.update(data);
  • HMACを16進数形式の文字列として返し、sha256= 形式のBufferで取得します
// const sigHashAlg = 'sha256';
const digest = Buffer.from(`${sigHashAlg}=${hmac.digest('hex')}`, 'utf8');
  • それぞれ取得したBufferを定数時間の文字列比較します
return crypto.timingSafeEqual(digest, sig);

以上で、シークレットトークンの検証ができます。
少し手間ですが、必ずシークレットトークンでの検証をすることをお勧めします。

参考にさせていただいたサイト

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?