見出し画像

作って学ぶ GitHub Apps

GitHub Appsが世に出て2年ほど立ちました。実際触ってみたことがある方は少ないのでしょうか。

今回はGitHub Appsについて調べて実際にサンプルアプリを登録、アクセストークンの発行&APIの実行までを試してみました。

そもそもGitHub Appsとは

IIJさんの記事が分かりやすかったので引用。

GithubではGithubと連携するツールを作るための仕組みとして、Personal access tokens を使う方法と、OAuthを利用する方法がありました。しかしいずれの仕組みもユーザーに紐付いたアクセスキーが発行されてしまうため、設定したユーザーが退職したり、異動したりしてアカウントが停止されたりするとアクセスキーも無効になり、API連携がエラーになってしまうことがあります。

そこで新しくGithub Appsという仕組みが導入され、リポジトリに紐づけて各種アプリケーションにアクセスを許可できるようになりました。今後はGithub Appsを用いて連携するのが推奨されています。GitHub Enterpriseでは2.13から利用が可能です。

従来のOAuth Appsはユーザに対してインストール - ユーザが削除されたりリポジトリへの権限を失うと消える - アクセストークンはユーザが取り消さない限り永久有効。

それとは違いGitHub Appsリポジトリに対してインストール - 正確にはリポジトリのオーナー(OrganizationやUser)に インストールして、配下のリポジトリに付与するようなイメージ。

簡単に言うとOAuth Appsはユーザ単位のアプリで、GitHub Appsはリポジトリ単位のアプリとなる。

GitHub Appsを登録

1. https://github.com/settings/apps へアクセス
2. New GitHub Appsで登録をする。

GitHub Appsを利用するに当たり必要なもの

1. Autorizetion Callback URL
    1. RepositoryへInstallした際の認証を行うURL
2. WebHook URL
    1. Subscribe to EventsでSubscribeしたEventがPOSTされてくるURL
3. Permissions
    1. GitHub Appsで何を行うか権限の設定を行う。ここで自分がやりたいことだけを選定する。
4. Where can this GitHub App be installed?
    1. Appsをインストールするユーザはどうするか
    2. 今回はお試しなのでOnly on This Account(自分のみ)にする。

今回はCLIでIssue をいじってみる想定なので、Issues のPermissionをRead & Writeに設定する。

アクセストークンの発行のための下準備

AppIDのメモ

AppsメニューGeneralのAbountに記載されているApp IDをどこかにメモします。これはアクセストークンの発行に必要です。

秘密鍵の生成

アクセストークンを発行するためにはJWT(Json Web Token)を利用するが、そのために必要な秘密鍵を生成してダウンロードする。

画像1

作成したAppsのInstall

実際に作成したAppをインストールする。

画像2

Installボタンを押すと、自分のどのRepositoryにInstallするかを選ぶことが出来る。今回はお試しなので、お試し用に作成した example-github-appsというRepositoryを利用する。

画像3

installations_idを抜き出す

アクセストークンを発行するために installations_id というIDが必要になるので、AppsのメニューにあるAdvancedから installations_id をメモする。installations_idは下記に記してあります。

X-GitHub-Event: installation の Payload -> `/installations/{installation_id}/access_tokens`の `installation_id`

APIを利用するためのアクセストークンを発行する。

いよいよ実装を始めます。GitHub Appsでは各GitHubのAPIを利用するためにアクセストークンを発行する必要があります。アクセストークンの発行にはJWT(Json Web Token)を利用します。

JWTを利用するためには前述した

1. 秘密鍵(pem file)
2. app_id
3. installation_id

が必要です。

実際にRubyで実装をしてみました。下記のPRがJsonWebTokenを利用してAccessTokenを取得するコードです。


require 'faraday'
require 'jwt'
require 'openssl'

require './constants'

class JsonWebTokenClient
 JWT_ALGORITHM = 'RS256'.freeze

 def initialize
   @client = Faraday.new(url: access_token_url)
 end

 def call
   result = @client.post do |request|
     request.headers['Authorization'] = "Bearer #{json_web_token}".freeze
     request.headers['Accept'] = 'application/vnd.github.machine-man-preview+json'.freeze
   end

   JSON.parse(result.body, symbolize_names: true)
 end

 private def access_token_url
   @access_token_url ||= "https://api.github.com/installations/#{ENV['INSTALLATION_ID']}/access_tokens".freeze
 end

 private def json_web_token
   JWT.encode(payload, private_key, JWT_ALGORITHM)
 end

 ##########################
 # example: https://developer.github.com/apps/building-github-apps/authenticating-with-github-apps/#authenticating-as-a-github-app
 #########################
 private def payload
   {
     # issued at time
     iat: Time.now.to_i,
     # JWT expiration time (10 minute maximum)
     exp: Time.now.to_i + (10 * 60),
     # GitHub App's identifier
     iss: ENV['APP_ID'],
   }
 end

 private def private_key
   @private_key ||= OpenSSL::PKey::RSA.new(File.read(Constants::PEM_FILE_PATH))
 end
end

下記の手順を踏むことで動きます。

1. RepositoryをCloneし、生成した秘密鍵をprivate-key.pemにrenameし、Repsitoryへコピーする。
2. 環境変数に下記を定義

export APP_ID=<メモした APP ID>
export INSTALLATION_ID=<メモしたINSTALLATION_ID>

その後、下記のようにコマンドを実行するとtokenが確認出来るかと思います。

$ bundle exec ruby -e "require './json_web_token_client'; puts JsonWebTokenClient.new.call.to_json" | jq .token
"HQTslJ4N4nduP0zkjFvrKAeloz3zqVQZbJasnBN7IveCxXZNFisTlzJPjjF7QQL98zXrP3szQ8stRB7cdatFQeQHZz1LSrsnV6Zb" # this token is a string generated for testing.

これでGitHub AppsでAPIを利用するためのアクセストークンを発行することができました。

アクセストークンを利用してAPIを実行する

最後にアクセストークンを利用してAPIを実行してみます。ここではAPIを経由してIssueを立ててみたいと思います。Issueを作成するAPI Documentは下記です。

実際にAPI Documentを元にRubyで実装してみました。下記のPRですべてのファイルが確認できます。


   require 'faraday'
   
   require './constants'
   require './json_web_token_client'
   
   ###########################################
   # api document: https://developer.github.com/v3/issues/#create-an-issue
   ###########################################
   class CreateIssueClient
     def initialize(title:, body:, assignees: [], labels: [])
       @title = title
       @body = body
       @assignees = assignees
       @labels = labels
   
       @client = Faraday.new(url: api_endpoint_url)
       @jwt_client = JsonWebTokenClient.new
     end
   
     def call
       result = @client.post do |request|
         request.headers['Authorization'] = "token #{access_token}".freeze
         request.headers['Accept'] = 'application/vnd.github.symmetra-preview+json'.freeze
   
         request.body = payload.to_json
       end
   
       JSON.parse(result.body, symbolize_names: true)
     end
   
     private def payload
       {
         title: @title,
         body: @body,
         assignees: @assignees,
         labels: @labels,
       }
     end
   
     private def api_endpoint_url
       "https://api.github.com/repos/#{Constants::OWNER_NAME}/#{Constants::REPOSITORY_NAME}/issues".freeze
     end
   
     private def access_token
       ret = @jwt_client.call
       ret[:token]
     end
   end

そして、下記のIssueが実際に上記のコードを利用して建てたIssueになります。

画像4

名前がGitHub Appsに登録したテスト用のAppsで、名前の右に botと記載されていることに注目してください。GitHub Apps経由で登録したということが分かると思います。

利用したコードはすべてRepositoryに公開していますので、参考までに。

参考資料

* https://dev.classmethod.jp/server-side/register-github-app-and-get-access-token/

Software Engineer. https://teitei-tk.com