WordPressのシステムの更新を、GitHubへのPushで自動化したくてその辺を調べてます。とりあえず、公式ドキュメントやらを読んで、ローカルにPHPで立てたサーバーでGitHubのWebhookを受け取ってみます。
目次
- 目次
- 前提
- ローカルサービスをインターネットからアクセスできるようにする
- GitHubでwebhookを設定
- PHPでテスト用サービスプログラムを作成
- PHPの組み込みサーバーを起動
- GitHub以外からのアクセスに反応しないことを確認
- まとめ
- 参考URL
前提
ローカルサービスをインターネットからアクセスできるようにする
GitHubのチュートリアルで紹介されているngrokを利用します。
- ngrok - downloadを開きます
- 動作させたいOS用のものをダウンロードします
- zipファイルを展開すると、実行ファイルが得られるので適当なフォルダーに入れます
- コマンドプロンプトやターミナルで、ngrokの実行ファイルを入れたフォルダーを開きます
- 以下で、
http://localhost:4567
をインターネットに公開します
ngrok http 4567
以下のような表示が出れば成功です。
Forwarding
とある横に表示されているhttp://3baeb856.ngrok.io
のURLで、8時間弱、インターネットからアクセスが可能になったということです。このURLをGitHubのWebhookに設定すれば実験ができます。このURLは毎回ランダムで変わりますので、表示されたURLに読み替えてください。
GitHubでwebhookを設定
GitHubに練習用のリポジトリーを作成して開いておきます。以下の操作をして、webhookを設定します。
- Settingsタブを開きます
- Webhooksをクリックします
- Add webhookをクリックします
- 認証が必要なので、GitHubのパスワードを入力してConfirm passwordをクリックします
- 以下のように設定します
- 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文字までのパスワードワードを生成できます
- Payload URL欄には、ngrokで公開したURLの後ろに
生成したパスワードは、GitHubとサーバーの双方で利用するのでどこかに書き写しておきましょう(以下に例示したものではなく、自分で生成したものに読み替えてください)。
ここまでは以下のような感じです。
- 引き続きその下を設定します
- Issuesイベントを設定するために、Let me select indivisual eventsを選択します
- Issuesにチェックを入れます。pushはそのままでも構いません
以上入力したら、Add webhookボタンをクリックして、webhookの設定完了です。
作成が成功するとGitHubのページ上に以下のようなメッセージが表示されます。
これで、練習用のリポジトリーにIssuesを登録すると、指定したhttp://????.ngrok.io/payload
宛に、GitHubからpayload(ペイロード)と呼ばれるデータが送信されます。
webhookの送信テスト
動きを試してみましょう。まだサーバーは設定していませんが、上記のURLにアクセスがあればエラーが表示されて、とりあえず何かが届いたことは確認できます。
- New issueボタンをクリックします
- 適当な見出しを入力して、Submit new issueボタンをクリックして、Issueを作成します
少しすると、ngrokにメッセージが届いて、以下のようなエラーが表示されます。
/payload
に対してPOSTとGETのメッセージが届いたけど処理できなかった、ということです。届いたメッセージを処理できるようにします。
PHPでテスト用サービスプログラムを作成
GitHubからのpayloadを受け取るPHPプログラムを準備します。それに先立って、GitHubのWebhookを作成する時にSecret
欄に入力したパスワードを環境変数に登録しておきます。Linuxやmacの場合は以下のような感じでターミナルで実行します。
export SECRET_TOKEN="43jsoauslakjerlkasakdjfldsew"
上記で設定している文字は仮のものなので、実際にはGitHubに設定したものに差し替えてください。
Windowsなら、システムの環境変数などで設定すれば良いでしょう。
続けて、PHPのプログラムを用意します。
- 開発するフォルダーを開いて、
payload
というフォルダーを作成します - 作成した
payload
フォルダーの中にindex.php
を作成します
- 以下のコードを入力して保存します
<?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
で移動します。
以下で、PHPの組み込みサーバーを起動します。
php -S 127.0.0.1:4567
準備ができました。GitHubの練習用リポジトリーで新しくIssueを追加するか、先に作成したIssueを編集してみてください。以下のようなメッセージがターミナルに表示されれば、何らかの処理が行われたことになります。
index.php
と同じフォルダーに、hook.log
というログファイルが出力され、その中に成功か失敗かが書かれているので、テキストエディターで開いてみてください。
「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かのチェックもしていますので、あとは必要な処理を認証成功時の場所に追加すれば良いでしょう。それはまた別でまとめます。