GitHub Apps + GitHub Actionsで必要なアクセス権限のみ付与した一時的なアクセストークンを発行する

2022.05.05

こんにちは、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を作成します。

.github/workflows/getAccessToken.yml

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}とすることによりトークン文字列がマスクされてないようにしています。

動作確認

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を叩くスクリプトを定期的に使うことがあるので、今回作成した仕組みが活用できそうです。

参考

以上