どうもこんにちは、ソーシャルゲーム事業部ゲーム技研の谷脇です。
この記事は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
- GitHub Appsを対象のOrganizationにインストール
- 秘密鍵をGitHub Appsの設定画面で作成しダウンロード
- App IDをIssuerクレームに含んだJSON Web Tokenを、先に作成した秘密鍵で署名する
- 何らかの方法でインストールしたOrganizationに対応するInstallation IDを取得する
- 作成した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
から見ることが出来ます。
もう一つは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も例に漏れず、コンストラクタに文字列として認証トークンを渡す形式です。そこで、文字列として評価されたときにトークンを発行し、有効期限が切れない間はそれをキャッシュして返し続け、切れたら再発行するようなオブジェクトを作るモジュールを作成しました。
このモジュールの詳細については、Perl Advent Calendar 2019の14日目の記事に記載しています。
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の認証トークンを使うコマンドを作りました。
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に変えた」です。