GitHub Apps + GitHub Actionsで必要なアクセス権限のみ付与した一時的なアクセストークンを発行する
こんにちは、CX事業本部 IoT事業部の若槻です。
今回は、GitHub Apps + GitHub Actionsで必要なアクセス権限のみ付与した一時的なアクセストークンを発行してみました。
なぜGitHub Appsを使うのか
GitHubではPersonal access tokensを使えばアクセストークンを簡単に発行することが出来ますが、スコープの粒度が粗く、操作可能なRepositoryの制限も出来ないため、必要以上のアクセス権限を付与してしまいがちです。
一方で、GitHub Appsを使用すれば、アクセス権限を細かく設定したアクセストークンを発行することが可能です。
今回はGitHub Appsを使用してアクセストークンを発行する仕組みをGitHub Actions上に作ってみます。
やってみた
GitHub Appsの作成
[https://github.com/settings/apps]で[New GitHub App]をクリック。
[GitHub App name]でアプリ名を指定します。(既存アプリと重複不可)
[Homepage URL]で適当なURLを指定します。
今回はWebhookは使わないため、[Webhook]で[Active]のチェックを外します。
[Repository permissions]で、GitHub Appsが操作可能なスコープを指定します。今回は[Issues]でAccess: Read-only
を選択します。この時、[Metadata]のAccess: Read-only
も自動でチェックが入ります。
[Only on this account]が選択されていることを確認し、[Create GitHub Apps]をクリック。
作成できました。
App IDは後ほど使用するので控えておきます。
Private keyの生成
GitHub Appsを使用してアクセストークンを取得するために必要なPrivate keyを生成します。
[Generate]の[Private keys]で[Generate a private key]をクリック。
Private keyが生成され、pemファイルがダウンロードされます。
pemファイルに記載されたPrivate keyは後ほど使用します。
GitHub Appsのインストール
GitHub Appsをアカウント(UserまたはOganization)上にインストールします。
[Install App]で先程のGitHub Appsをインストールします。
GitHub AppsがアクセスできるRepositoryを指定して、インストールします。
インストールできました。
シークレットの設定
App IDとPrivate keyを、GitHub Actionsを設定するRepositoryのシークレットにそれぞれAPP_ID
およびPRIVATE_KEY
という名前で設定します。
これら情報を使用してGitHub Appsよりアクセストークンが取得できるようになります。
GitHub Actions Workflowの作成
対象のRepositoryのデフォルトブランチに次のWorfflow Configを作成します。
on: workflow_dispatch jobs: job: runs-on: ubuntu-latest steps: - name: Generate token id: generate_token uses: tibdex/github-app-token@v1 with: app_id: ${{ secrets.APP_ID }} private_key: ${{ secrets.PRIVATE_KEY }} - name: Output token env: TOKEN: ${{ steps.generate_token.outputs.token }} run: | echo "Access Token: ${TOKEN:4}"
- イベントのトリガーを
workflow_dispatch
として手動実行できるようにしています。 - 1つ目のジョブで、tibdex/github-app-token actionを使用してGitHub Appsよりアクセストークンを取得しています。
- 取得したアクセストークンを2つ目のステップで画面出力しています。
${TOKEN:4}
とすることによりプレフィクス(ghs_
)を削除してトークン文字列がマスクされないようにしています。
動作確認
Workflowを手動実行して、画面出力されたアクセストークンを控えます。
このアクセストークンを使用していくつかGitHub APIを叩いてみます。
権限を付与したRepositoryからのIssue取得は実行できました。
$ OWNER=<Owner> $ REPO=<Repository> $ GH_API_TOKEN=ghs_"<Access Token>" $ curl \ -H "Accept: application/vnd.github.v3+json" \ -H "Authorization: token ${GH_API_TOKEN}" \ https://api.github.com/repos/${OWNER}/${REPO}/issues/1300 { "url": "https://api.github.com/repos/cm-rwakatsuki/devio/issues/1300", "repository_url": "https://api.github.com/repos/cm-rwakatsuki/devio", "labels_url": "https://api.github.com/repos/cm-rwakatsuki/devio/issues/1300/labels{/name}", "comments_url": "https://api.github.com/repos/cm-rwakatsuki/devio/issues/1300/comments", "events_url": "https://api.github.com/repos/cm-rwakatsuki/devio/issues/1300/events", "html_url": "https://github.com/cm-rwakatsuki/devio/issues/1300", "id": 1224132100, "node_id": "I_kwDOEbgpus5I9sYE", "number": 1300, "title": "[MacBook] Pythonでウィンドウを操作する(PyWinCtl)", //中略 }
同じRepositoryのIssueにコメントを追加しようとすると、Resource not accessible by integration
となります。
$ curl \ -H "Accept: application/vnd.github.v3+json" \ -H "Authorization: token ${GH_API_TOKEN}" \ https://api.github.com/repos/${OWNER}/${REPO}/issues/1300 \ -d '{"body":"Me too"}' { "message": "Resource not accessible by integration", "documentation_url": "https://docs.github.com/rest/reference/issues#update-an-issue" }
同じRepositoryからPull Request一覧を取得しようとすると、Not Found
となります。
$ curl \ -H "Accept: application/vnd.github.v3+json" \ -H "Authorization: token ${GH_API_TOKEN}" \ https://api.github.com/repos/${OWNER}/pulls { "message": "Not Found", "documentation_url": "https://docs.github.com/rest/reference/repos#get-a-repository" }
権限を付与していないRepositoryからIssueを取得しようとすると、Not Found
となります。
$ REPO2=<Other Repository> $ curl \ -H "Accept: application/vnd.github.v3+json" \ -H "Authorization: token ${GH_API_TOKEN}" \ https://api.github.com/repos/${OWNER}/${REPO2}/issues/1 { "message": "Not Found", "documentation_url": "https://docs.github.com/rest/reference/issues#get-an-issue" }
GitHub Appsに権限を付与したRepositoryのスコープ内の操作のみ実施できることが確認できました!
トークンの有効期限
アクセストークンの有効期限は既定で8時間です。一時トークンなので万が一トークンが侵害された時にも永続的には使えないので安心ですね。
User-to-server tokens created by a GitHub App will expire after eight hours by default. Owners of GitHub Apps can configure their apps so that user-to-server tokens do not expire.
GitHub APIで期限切れのアクセストークンを使ってみると、Bad credentials
となりアクセス出来ませんでした。
$ curl \ -H "Accept: application/vnd.github.v3+json" \ -H "Authorization: token ${GH_API_TOKEN}" \ https://api.github.com/repos/${OWNER}/${REPO}/issues/1300 { "message": "Bad credentials", "documentation_url": "https://docs.github.com/rest" }
GitHub Actionsを実行するRepositoryへは必要メンバー以外は参加させないようにする
今回ちょうど良いactionがあったためGitHub Actions上でアクセストークンを取得できるようにしましたが、一時トークンとはいえ、RepositoryのGitHub Actionsへのアクセス権があるユーザーへは過去に発行されたアクセストークンが見えてしまいます。よってユーザーによっては権限以上の操作が可能となる恐れがあります。
GitHub Actionsを実行するRepositoryはどこでも良いため、必要メンバーのみを参加させた、アクセストークン取得用のRepositoryを別途用意すると良いかと思います。
おわりに
GitHub Apps + GitHub Actionsで必要なアクセス権限のみ付与したアクセストークンを発行してみました。
Personal access tokensの権限は強すぎるのではと常々思っていので、今回アクセス権限を限定する方法を確認できて良かったです。決まったGitHub APIを叩くスクリプトを定期的に使うことがあるので、今回作成した仕組みが活用できそうです。
参考
以上