tanaka's Programming Memo

プログラミングについてのメモ。

GitHubのWebhookをPHPで受け取る練習

WordPressのシステムの更新を、GitHubへのPushで自動化したくてその辺を調べてます。とりあえず、公式ドキュメントやらを読んで、ローカルにPHPで立てたサーバーでGitHubのWebhookを受け取ってみます。

目次

前提

  • GitHubにアカウントを持っていること
  • PHPがインストールされていること

ローカルサービスをインターネットからアクセスできるようにする

GitHubチュートリアルで紹介されているngrokを利用します。

  • ngrok - downloadを開きます
  • 動作させたいOS用のものをダウンロードします

f:id:am1tanaka:20180308210840p:plain

  • zipファイルを展開すると、実行ファイルが得られるので適当なフォルダーに入れます
  • コマンドプロンプトやターミナルで、ngrokの実行ファイルを入れたフォルダーを開きます
  • 以下で、http://localhost:4567をインターネットに公開します
ngrok http 4567

以下のような表示が出れば成功です。

f:id:am1tanaka:20180308212045p:plain

Forwardingとある横に表示されているhttp://3baeb856.ngrok.ioのURLで、8時間弱、インターネットからアクセスが可能になったということです。このURLをGitHubのWebhookに設定すれば実験ができます。このURLは毎回ランダムで変わりますので、表示されたURLに読み替えてください。

GitHubでwebhookを設定

GitHubに練習用のリポジトリーを作成して開いておきます。以下の操作をして、webhookを設定します。

  • Settingsタブを開きます

f:id:am1tanaka:20180308203456p:plain

  • Webhooksをクリックします

f:id:am1tanaka:20180308203705p:plain

  • Add webhookをクリックします

f:id:am1tanaka:20180308203856p:plain

  • 認証が必要なので、GitHubのパスワードを入力してConfirm passwordをクリックします

f:id:am1tanaka:20180308204056p:plain

  • 以下のように設定します
    • Payload URL欄には、ngrokで公開したURLの後ろに/payloadをくっつけた文字列を設定します。上記の例だとhttp://3baeb856.ngrok.io/payloadです
    • Content Typeは、application/jsonにして、JSONで受け取るようにします
    • Secretは、セキュリティーのための文字列です。30〜40文字程度のランダムな半角英数記号を設定します。自分で適当に入力しても良いですし、https://identitysafe.norton.com/ja/password-generator#などで自動生成しても良いでしょう。このサービスでは32文字までのパスワードワードを生成できます

f:id:am1tanaka:20180308213150p:plain

生成したパスワードは、GitHubとサーバーの双方で利用するのでどこかに書き写しておきましょう(以下に例示したものではなく、自分で生成したものに読み替えてください)。

f:id:am1tanaka:20180308213321p:plain

ここまでは以下のような感じです。

f:id:am1tanaka:20180308213459p:plain

  • 引き続きその下を設定します
    • Issuesイベントを設定するために、Let me select indivisual eventsを選択します
    • Issuesにチェックを入れます。pushはそのままでも構いません

f:id:am1tanaka:20180308213711p:plain

以上入力したら、Add webhookボタンをクリックして、webhookの設定完了です。

f:id:am1tanaka:20180308213925p:plain

作成が成功するとGitHubのページ上に以下のようなメッセージが表示されます。

f:id:am1tanaka:20180308214217p:plain

これで、練習用のリポジトリーにIssuesを登録すると、指定したhttp://????.ngrok.io/payload宛に、GitHubからpayload(ペイロード)と呼ばれるデータが送信されます。

webhookの送信テスト

動きを試してみましょう。まだサーバーは設定していませんが、上記のURLにアクセスがあればエラーが表示されて、とりあえず何かが届いたことは確認できます。

f:id:am1tanaka:20180308214722p:plain

  • New issueボタンをクリックします

f:id:am1tanaka:20180308214808p:plain

  • 適当な見出しを入力して、Submit new issueボタンをクリックして、Issueを作成します

f:id:am1tanaka:20180308215031p:plain

少しすると、ngrokにメッセージが届いて、以下のようなエラーが表示されます。

f:id:am1tanaka:20180308215203p:plain

/payloadに対してPOSTとGETのメッセージが届いたけど処理できなかった、ということです。届いたメッセージを処理できるようにします。

PHPでテスト用サービスプログラムを作成

GitHubからのpayloadを受け取るPHPプログラムを準備します。それに先立って、GitHubのWebhookを作成する時にSecret欄に入力したパスワードを環境変数に登録しておきます。Linuxmacの場合は以下のような感じでターミナルで実行します。

export SECRET_TOKEN="43jsoauslakjerlkasakdjfldsew"

上記で設定している文字は仮のものなので、実際にはGitHubに設定したものに差し替えてください。

Windowsなら、システムの環境変数などで設定すれば良いでしょう。

続けて、PHPのプログラムを用意します。

  • 開発するフォルダーを開いて、payloadというフォルダーを作成します
  • 作成したpayloadフォルダーの中にindex.phpを作成します

f:id:am1tanaka:20180309000412p:plain

  • 以下のコードを入力して保存します
<?php

// @copyright 2018 YuTanaka@AmuseOne
// @license https://github.com/am1tanaka/deploy-php/blob/master/LICENSE MIT
// 参考URL https://qiita.com/oyas/items/1cbdc3e0ac35d4316885

// ログファイルをこのフォルダーと同じ場所のhook.logに設定
$LOG_FILE = dirname(__FILE__).'/hook.log';

// 必要なデータを取得
$secret = getenv("SECRET_TOKEN");
$sig = array_key_exists('HTTP_X_HUB_SIGNATURE', $_SERVER) ? $_SERVER['HTTP_X_HUB_SIGNATURE'] : "";
$event = array_key_exists('HTTP_X_GITHUB_EVENT', $_SERVER) ? $_SERVER['HTTP_X_GITHUB_EVENT'] : "";
$body = file_get_contents('php://input');
$hmac = 'sha1='.hash_hmac('sha1', $body, $secret);

// 送られてきたSIGNATUREの長さが違う場合は
// セキュリティー対策で本来の長さの文字列に差し替える
if (strlen($sig) !== strlen($hmac)) {
    $sig = "";
    for ($i=0 ; $i<strlen($hmac) ; $i++) {
        $sig .= "a";
    }
}

// 認証
if (hash_equals($hmac, $sig)) {
    // 認証成功
    $payload = json_decode($body, true);
    file_put_contents($LOG_FILE, date("[Y-m-d H:i:s]")." ".$_SERVER['REMOTE_ADDR']." git issue recieved.\n", FILE_APPEND|LOCK_EX);

    // これ以降に実行したい処理を書く
}
else {
    // 認証失敗
    file_put_contents($LOG_FILE, date("[Y-m-d H:i:s]")." invalid access: ".$_SERVER['REMOTE_ADDR']."\n", FILE_APPEND|LOCK_EX);
}

 ?>

基本的な部分は@oyas様のGitHubのwebhookを受け取って、自動でgit pullするスクリプト - Qiitaから拝借しました。公式ドキュメントWebhooks | GitHub Developer Guideの記載に従って、タイミングアタックを防ぐためにハッシュ値の比較はhash_equalsに差し替えて、文字長が異なる時のチェックも加えたものです。

プログラムが準備できたので、サーバーを起動して動作確認をしましょう。

PHPの組み込みサーバーを起動

ターミナルでindex.phpを作成したpayloadフォルダーの一つ親のフォルダー(以下の場合、am1hpフォルダー)にcdで移動します。

f:id:am1tanaka:20180309000412p:plain

以下で、PHPの組み込みサーバーを起動します。

php -S 127.0.0.1:4567

準備ができました。GitHubの練習用リポジトリーで新しくIssueを追加するか、先に作成したIssueを編集してみてください。以下のようなメッセージがターミナルに表示されれば、何らかの処理が行われたことになります。

f:id:am1tanaka:20180309001507p:plain

index.phpと同じフォルダーに、hook.logというログファイルが出力され、その中に成功か失敗かが書かれているので、テキストエディターで開いてみてください。

f:id:am1tanaka:20180309001716p:plain

「git issue recieved.」と表示されていれば、GitHubからの正規なIssueイベントを受け取って、それを認証できたということです。

GitHub以外からのアクセスに反応しないことを確認

最後に、不正なアクセスをして、アクセスが拒否されることを確認します。Webブラウザーで以下を開いてください。

http://localhost:4567/payload

白い何も書かれていない画面が表示されれば動作完了です。このアクセスには正しいシグネチャーを持たないのでアクセスは拒否されているはずです。hook.logを確認してみてください。今度は以下のように「invalid access」というメッセージが書き込まれます。

[2018-03-09 00:19:25] invalid access: 127.0.0.1

つまり、認証が通らなかったので動作を拒否したということになります。

まとめ

以上でGitHubからWebhookを受け取って、認証する仕組みをローカル環境でテストしました。起動していたngrokとPHPの組み込みサーバーを[Ctrl]+[C]キーで停止して、ターミナルを閉じて構いません。

正規のWebhookかのチェックもしていますので、あとは必要な処理を認証成功時の場所に追加すれば良いでしょう。それはまた別でまとめます。

参考URL