GitHub APIを使うBotたちのGitHub Appsへの移行

どうもこんにちは、ソーシャルゲーム事業部ゲーム技研の谷脇です。

この記事はTech KAYAC Advent Calendar 2019 Migration Trackの14日目の記事です。13日目はAmazon S3 Signature V2 廃止対応にまつわるあれこれでした。

従来のGitHub APIを使うBot

カヤックではGitHubを使っています。また、各プロジェクトでは自動化されたワークフローの一環として、Slack上でのコマンドからPull Requestを作成したり、レビュワースロットを実行したり、勝手に手順書を生成してissueに貼るなどしています。

こういったBotたちはGitHub上のリソースを操作するために、GitHub APIを使用しています。Organization内のPrivateリポジトリを操作するためには、認証のためにGitHub Tokenが必要です。

Machine userのGitHubアカウント

GitHub APIを叩く際にはGitHubの認証トークンを使います。認証トークンは、アカウントの設定ページから発行できるものです。

大事なのは、この認証トークンは1つのアカウントに紐づくもの、ということです。つまり、Botにもそれぞれに人間のユーザのようなGitHubアカウントを作成してトークンを発行して付与しなければいけない、ということになります。

従来はプロジェクトごとにBotアカウントを作成し、リポジトリのコラボレーターにBotを追加して、BotアカウントのトークンでAPIを実行していました。

BotがGitHubアカウントを持つ際の問題点

BotがGitHubアカウントを持つことでこれまで対応してきましたが、

  • 1つのアカウントをチーム内で共有して管理するモデルの危うさ
  • Botアカウントの管理がOrganizationから出来ない
  • GitHubのEnterpriseプランだとBotアカウントも1ヶ月あたり21ドルかかり、Botの数が増えるとばかにならないため
  • 1つのBotアカウントを複数プロジェクトでまたいで使うにはトークンが持つ権限が強すぎる

などなど、様々なデメリットがありました。そこで、ユーザではなくGitHub Appsの認証情報でAPIを実行しようと考えました。

普通のBotアカウントとGitHub Appsの違い

GitHub Appsはユーザアカウントとは違います。ユーザもしくはOrganizationが作成・管理を行うAppという位置づけです。似たような概念としてOAuth Appsがありますが、OAuth Appsに比べると出来ることが増えています。

以下にこれらの比較を示します。

項目 ユーザアカウント OAuth Apps GitHub Apps
誰が作るか ユーザ自身がサインアップ ユーザもしくはOrganizationが作る ユーザもしくはOrganizationが作る
複数ユーザでの管理 現実的に不可能 OrganizationのAdminのみが可能 OrganizationのAdmin以外も管理権限を付与出来る
一般ユーザとしてのAPI実行 可能 OAuthで可能 OAuthで可能
一般ユーザに紐付かないAPI実行 Botアカウントであれば可能 不可能 Installation Tokenで可能
GitHub EnterpriseでのSeat課金 される されない されない
トークンの有効期限 なし あり(OAuth) あり(1時間)
リポジトリの制限設定 ユーザが属するリポジトリ全てのみ OAuth認証したユーザが見れるリポジトリ全てのみ InstallしたOrganizationの管理画面で制限可能

また、GitHub AppsはユーザやOrganizationが作成するものですが、作成したユーザやOrganization以外が、同じGitHub Appsをインストールすることも可能です。一般に公開されているGitHub AppsはMarketplaceでもGitHub Actionsとともに並んでいます。

GitHub Appsは認証トークンの更新をし続けなければ使い続けられない反面、管理の面やコストの面でメリットが大きいと判断しました。

というわけで、既存のアカウントを作って運用していたBotをGitHub Appsに移行することにしました。

GitHub Appsでの認証トークンの取得方法

普通のユーザでは、設定画面から認証トークンを発行すればそれがずっと使えますが、GitHub Appsの場合は違います。GitHub Appsの認証トークンの発行は以下の段階を踏んで行います。

参考: Authenticating with GitHub Apps

  1. GitHub Appsを対象のOrganizationにインストール
  2. 秘密鍵をGitHub Appsの設定画面で作成しダウンロード
  3. App IDをIssuerクレームに含んだJSON Web Tokenを、先に作成した秘密鍵で署名する
  4. 何らかの方法でインストールしたOrganizationに対応するInstallation IDを取得する
  5. 作成したJWTをAuthorizationヘッダに含めて https://api.github.com/app/installations/:installation_id/access_token を叩く

最後のAPIのレスポンスで1時間の有効期限がある認証トークンを取得できます。このトークンでは、ユーザの認証トークンと同様に、ほとんどのAPIに対して使用することが出来ます。

Installation IDの取得方法

Organizationに対応するInstallation IDを調べる方法はいくつかあります。

一つは、インストール時にGitHub AppsのWebhookを見ることです。あらかじめWebhookのサーバを立ててログを取っていなくても、GitHub AppsのAdvanced -> Recent Deliversから見ることが出来ます。

f:id:mackee_w:20191213163412p:plain

もう一つはhttps://api.github.com/app/installationsというAPIを叩いて、インストールされたOrganization一覧を表示して、合致するOrganizationのIDを抜き出す方法です。このAPIを叩く際の認証方法は、認証トークンを取得する際と同じJWTで可能です。

各Botでの対応

ほとんどのGitHub APIを使うためのライブラリはGITHUB_TOKEN環境変数に認証トークンを入れたり、コンストラクタの引数として認証トークンを渡すなどの手法とをっています。

ただ、これらはGitHub Appsの有効期限があるトークンには対応していません。ですので、トークンを定期的に更新する機構が必要です。

Perlの場合

カヤックでは複数のプロジェクトでPerlを使っており、BotもPerlで書かれています。PerlでGitHubのAPIを叩く際には、Pithubというライブラリを使用しています。

Pithubも例に漏れず、コンストラクタに文字列として認証トークンを渡す形式です。そこで、文字列として評価されたときにトークンを発行し、有効期限が切れない間はそれをキャッシュして返し続け、切れたら再発行するようなオブジェクトを作るモジュールを作成しました。

GitHub::Apps::Auth

このモジュールの詳細については、Perl Advent Calendar 201914日目の記事に記載しています。

Goの場合

Goでサーバアプリケーションを書いているプロジェクトでは、BotもGoで書いています。GoでGitHubのAPIを叩く際には、github.com/google/go-githubを使っています。

このパッケージでもGitHub Appsのトークンはそのままでは使えません。Perlの場合と同様に、有効期限が切れるごとに更新するような工夫が必要です。

まさにそのようなことをやってくれるパッケージが、github.com/bradleyfalzon/ghinstallationです。

このパッケージで、まずHTTPのClientを作ってそれをgo-githubのNewに渡すだけでGitHub Appsの認証トークンを使ってAPIを叩くことが出来ます。

gitコマンドの場合

Botの中には、gitリポジトリをチェックアウトして操作した上でコミットするものも存在します。単一のリポジトリのみの操作であれば書き込み許可したDeploy Keyでも構わないのですが、Organization内の別のprivate repositoryをsubmoduleするなど、一度に複数のリポジトリを扱いたい場合には、Deploy Keyは使えません。

GitHub Appsで発行した認証トークンは普通のユーザの認証トークンと同様に、httpsプロトコルでのgitの操作時の認証にも使えます。ただ、毎回発行しては入力するというのも手間なので、もともとgitに存在するcredential helperの仕組みを利用して、GitHub Appsの認証トークンを使うコマンドを作りました。

git-credential-github-apps

Windows, Mac, Linuxそれぞれに対応したビルド済みバイナリがReleasesにありますので、それをダウンロードし、パスが通るところに置いてお使いください。

このコマンドはもとも存在する、Python製のgit-credential-github-app-authのGo移植版であるとも言えますが、細かい使い方は違いますのでご注意ください。

使い方としては、パスが通るところにgit-credential-github-appsを設置した上で、

$ git config --global credential.helper 'github-apps -privatekey <path to private key> -appid <App ID> -login <organization>'

と設定すれば、GitHub Appsの認証トークンでgitの操作が行われます。loginはOrganizationの名前を入れてください。git-credential-github-appsがInstallation IDを自動で検索します。

また、git-credential-github-app-authとは違い、1時間の有効期限の間であればファイルにトークンをキャッシュして使用するようになっています。

まとめ

  • ユーザアカウントのトークンを使うのに比べてGitHub Appsの方が管理の面などでメリットがある
  • 認証トークンの作り方が違うのと、定期的に再発行しないといけないので注意
  • 定期的にトークン再発行して今までのモジュールやgitコマンドを使う方法について説明しました

この記事を見て試してみて、うまく動かないよという情報があれば教えてください。直したりアドバイスなど力になれることがあるかもしれません。

明日はid:handlenameさんの「s3_backup(Lambda)をCross Reagion Replicationに変えた」です。