🐈

GitHub Appのインストールアクセストークンを試してみる

2024/12/05に公開

こんにちは。
株式会社ココナラのシステムプラットフォーム部インフラ・SREチームのかとりょーと申します。

この記事ではGitHubのアクセストークンに触れつつ、実際にGitHub Appのインストールアクセストークンを利用してリポジトリをcloneする流れを紹介します。

経緯

弊社ではCI/CDのツールの一つとしてCircleCIを利用しています。
CIの中で他のリポジトリと連携を行うことがあるのですが、外部サービスなのでGitHubとの認証が必要になります。
GitHubの認証にはいくつか方法がありますが、GitHub Appのインストールトークンに触れておもしろかったのでこの記事にしてみようと思いました。

GitHubのアクセストークンとは

プログラムからGitHub APIを利用する場合やコマンドラインからGitHubにアクセスする時に、パスワードの代わりにアクセストークンを認証に使用します。
アクセストークンは必要な操作権限だけを設定することができるので、パスワードとユーザー名を直接利用するよりも安全です。

GitHubのアクセストークンの種類

GitHubのトークンにはいくつか種類があり、用途や特性が異なりますが
今回は下記の5つに絞って簡単に紹介します。

  • Personal Access Token (Classic)
    • ユーザーに紐づき、発行したユーザーとして認証に利用できる。
    • 細かいスコープの制限が難しい。
    • ユーザー個人で複数発行できるので管理が難しい。
  • Fine-grained Personal Access Token
    • Classicと同じPersonal access Tokenなのでユーザーに紐づく。
    • Classicと異なる特徴としては、特定のリポジトリやリソースに対し細かくスコープの制限がかけられるようになっている。
  • OAuth Access Token
    • OAuth Appで発行できるトークンで、外部のプログラムからのGitHubの操作に利用される。
    • こちらもユーザーに紐づく。
    • GitHub Appのトークンを利用することが推奨されている。
  • GitHub App Installation Access Token
    • ユーザーではなくアプリとして扱われる。
    • アプリごとに細かい制限設定ができる。
    • 使用するタイミングで都度発行する。
    • トークンの有効期限が短く漏洩リスクが少ない。
  • GITHUB_TOKEN
    • Actionsのワークフロー実行中に取得できる。
    • 有効期限はワークフロー実行中のみなので漏洩リスクが少ない。
    • ワークフローの中で権限設定できる。

どのアクセストークンを利用すれば良いか

特別な理由がなければこの2つのアクセストークンを利用するのが良いです。

  • GITHUB_TOKEN
  • GitHub App Installation Access Token

理由としては下記です。

  • ユーザーに紐づかないので、ユーザーが削除されても問題ない。
  • トークンの有効期限が短く漏洩リスクが少ない。
  • 他のアクセストークンに比べきめ細やかな権限設定ができる。

GITHUB_TOKENはActionsのワークフロー内のみしか利用することができません。
また、GitHub Appのインストールアクセストークンはまだ全機能に対応できておらず、機能によっては使えないことがあります。
このようにActionsは使わない、かつGitHub Appが対応できていない場合は、Personal Access Tokenを利用することになります。

今回私はCircleCIで利用することを想定していたので、Github Appのインストールアクセストークンを使うことにしました。

GitHub Appについて

GitHub Appは組織とユーザーどちらでも作成することができます。
組織やリポジトリにそのGitHub Appをインストールすることで、ユーザーに代わってGitHub Appが持つ権限によってGitHubを操作することができるようになります。

実際にGitHub Appを試してみる

GitHub Appの作成

  1. GitHubの設定画面から「Settings > Developer settings > GitHub Apps」を開きます。
    githubapp
  2. 「New GitHub App」をクリックしてAppの設定を行います。
  3. 「App name」に任意の名前を入力してください。
  4. 「Homepage URL」にアプリケーションのURLを入力してください。(http://localhost でも大丈夫です)
    githubapp setting
  5. 「Permissions」で必要な権限を設定してください。(今回私はクローンしたいだけなので「Repository permissions」のContentsとMetadataにread権限を設定します。)
    githubapp setting2
    githubapp setting3
    githubapp setting4
  6. 最後に「Create GitHub App」というボタンがあるのでクリックして作成を終えます。

GitHub Appのインストールアクセストークンを生成するために必要な情報の確認

必要な情報は下記の3点です。

  1. Client ID(AppIDでも可能ですが、ClientIDの方が良いです)
  2. 秘密鍵(PEM)
  3. Install ID

1のClient IDは作成したGitHub Appに下記のように表示されているのでこれを控えておきます。
githubapp id

2の秘密鍵はGitHub Appを作成後にダウンロードできるので、安全な場所に保存してください(1行の文字列にしておくとCircleCIでも利用できるので後でbase64でエンコードします)。
githubapp id

3のInstallIDは黒塗りになっていますがGitHubAppの設定画面のURLの一番後ろの数字8桁なのでこちらも控えておきます。
組織で使う場合とユーザーで利用する場合でURLが異なります。
組織: github.com/organizations/組織名/settings/installations/12345678
ユーザー: github.com/settings/installations/12345678
githubapp id

JWTについて

インストールアクセストークンを取得するためにはJWTを作る必要があります。
このJWTをGitHub AppのAPIに渡すことでインストールアクセストークンが返却されるというわけです。

JWT作成はこのようなイメージです。
ヘッダーとペイロードをbase64urlでエンコードし、それぞれ「.」で結合します。
結合したヘッダーとペイロードを秘密鍵で署名し、さらに「.」で結合しJWTを作成します。
jwt image

インストールアクセストークンを取得する

  1. GitHub App作成時にダウンロードした秘密鍵をbase64エンコードして環境変数に保存します。
export GITHUB_KEY_ENCODED="$(cat /path/to/your/key.private-key.pem | base64)"
  1. Client IDを環境変数に保存します。
export GITHUB_CLIENT_ID="xxxxxxxx"
  1. Install IDを環境変数に保存します。
export GITHUB_INSTALLATION_ID="12345678"
  1. 下記のコードを実行するとインストールアクセストークンを取得することができます。
    ※bashで実行しないとエラーになります。
    ※jqを利用しているのでなければインストールしてください。

bash get_github_app_token.sh
GitHub App Token: ghs_DEHOYxxxxxxxxxxxWvOfHpplG2YB4w6

#レスポンスの中身。
#repository_selectionはallにしているが、リポジトリごとに制限可能。制限する場合はselectと表示される
{
  "token": "ghs_DEHOYxxxxxxxxxxxWvOfHpplG2YB4w6",
  "expires_at": "2024-11-25T10:33:35Z",
  "permissions": {
    "contents": "read",
    "metadata": "read"
  },
  "repository_selection": "all"
}


#!/bin/bash

set -eo pipefail

# 環境変数
installation_id="$GITHUB_INSTALLATION_ID"
client_id="$GITHUB_CLIENT_ID"
pem="$(echo $GITHUB_KEY_ENCODED | base64 -d)"

now=$(date +%s)
iat=$((now - 60)) # JWTの作成時刻は60秒前
exp=$((now + 600)) # 有効期限10分

base64url_encode() {
    openssl base64 | tr -d '=' | tr '/+' '_-' | tr -d '\n'
}

create_header() {
    local header_json='{
        "typ": "JWT",
        "alg": "RS256"
    }'

    echo -n "$header_json" | base64url_encode
}

create_payload() {
    local payload_json="{
        \"iat\": ${iat},
        \"exp\": ${exp},
        \"iss\": \"${client_id}\"
    }"

    echo -n "$payload_json" | base64url_encode
}

sign_payload_with_key() {
    local header_payload="$1"
    echo -n "$header_payload" | openssl dgst -sha256 -sign <(echo -n "$pem") | base64url_encode
}

get_github_token() {
    local jwt="$1"
    response=$(curl --request POST \
        --url "https://api.github.com/app/installations/${installation_id}/access_tokens" \
        --header "Accept: application/vnd.github+json" \
        --header "Authorization: Bearer ${jwt}" \
        --header "X-GitHub-Api-Version: 2022-11-28" \
        --silent)

    echo "${response}"
}

header=$(create_header)
payload=$(create_payload)
signature=$(sign_payload_with_key "${header}.${payload}")
jwt="${header}.${payload}.${signature}"

response=$(get_github_token "${jwt}")
token=$(echo "${response}" | jq -r '.token')

echo "GitHub App Token: ${token}"

インストールアクセストークンを利用してcloneしてみる

取得したインストールアクセストークンを利用してcloneしてみます。
GitHub Appのインストールアクセストークンは下記のようにghs_が頭についています。
GitHub App Token: ghs_DEHOYxxxxxxxxxxxWvOfHpplG2YB4w6

#git clone https://x-access-token:<GITHUB_TOKEN>@github.com/<OWNER>/<REPO>.gitという形
% git clone https://x-access-token:ghs_DEHOYxxxxxxxxxxxWvOfHpplG2YB4w6@github.com/xxxxxxxxxxxxx/githubapp-test.git
Cloning into 'githubapp-test'...
remote: Enumerating objects: 3, done.
remote: Counting objects: 100% (3/3), done.
remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0)
Receiving objects: 100% (3/3), done.

問題なくcloneできました!!

コードについて

利用したコードについて簡単に解説します。

こちらのGitHubのドキュメントを参考に作成しました。
Generating a JSON Web Token (JWT) for a GitHub App
Generating an installation access token for a GitHub App

まず、先ほど設定した環境変数を取得しています。
PEMはあらかじめbase64にエンコードしていたので一度デコードしています。

installation_id="$GITHUB_INSTALLATION_ID"
client_id="$GITHUB_CLIENT_ID"
pem="$( echo $GITHUB_KEY_ENCODED | base64 -d )"

JWTを作成するために必要な値を準備します。
有効期限のexpは10分にしていますが、さらに短くすることも可能です。
こちらにも書いてある通り、iatの値は60秒前に設定することが推奨されています。

now=$(date +%s)
iat=$((now - 60)) # JWTの作成時刻は60秒前
exp=$((now + 600)) # 有効期限10分

base64urlエンコードする関数です。
base64にエンコードした後、「=」と「\n」は削除し、「/」を「_」に「+」は「-」に変換しています。

base64url_encode() {
    openssl base64 | tr -d '=' | tr '/+' '_-' | tr -d '\n'
}

ヘッダーとペイロードを秘密鍵で署名する関数です。

sign_payload_with_key() {
    local header_payload="$1"
    echo -n "$header_payload" | openssl dgst -sha256 -sign <(echo -n "$pem") | base64url_encode
}

JWTを作成します。JWTはヘッダーとペイロードと署名したヘッダーとペイロードを「.」で繋いだ形で作成します。

header=$(create_header)
payload=$(create_payload)
signature=$(sign_payload_with_key "${header}.${payload}")
jwt="${header}.${payload}.${signature}"

GitHub Appのインストールアクセストークンを取得するAPIのBearerにJWTを入れて実行します。
レスポンスからインストールアクセストークンだけ抜き取って出力します。

get_github_token() {
    local jwt="$1"
    response=$(curl --request POST \
        --url "https://api.github.com/app/installations/${installation_id}/access_tokens" \
        --header "Accept: application/vnd.github+json" \
        --header "Authorization: Bearer ${jwt}" \
        --header "X-GitHub-Api-Version: 2022-11-28" \
        --silent)

    echo "${response}"
}

#省略

response=$(get_github_token "${jwt}")
token=$(echo "${response}" | jq -r '.token')

echo "GitHub App Token: ${token}"

最後に

ここまで読んでいただき、ありがとうございました!
GitHub Appのインストールアクセストークンを利用する方法は他の方法と比較すると難しいですが、プログラムからGitHubを操作するときに安全に利用できそうですね。

ココナラでは一緒に働く方を募集しています。
インフラ・SREだけでなく幅広く募集していますので、ご興味ある方はぜひご応募ください!

ブログの内容への感想、カジュアルにココナラの技術組織の話をしてみたい方はこちら
https://open.talentio.com/r/1/c/coconala/pages/70417
※ブログ閲覧者の方限定のカジュアル面談の応募フォームとなります!

エンジニアの募集職種一覧はこちら
https://coconala.co.jp/recruit/engineer

Discussion