AWS CodeBuildのGitHub Apps対応
本記事は「MIXI DEVELOPERS Advent Calendar 2024」のシリーズ2 2日目の記事です。
株式会社MIXIでサロンスタッフ予約サービス「minimo」のバックエンドエンジニアをしている鈴木です。
minimoで使用しているAWS CodeBuildのGitHub Apps対応をどのように行ったかについて書いていきます。
経緯
GitHub Enterprise Cloud (以下、GHEC) 導入を進めています。
これに伴い、CI/CDでのGitHubの操作にマシンユーザーのpersonal access tokenを使用している場合の代替案として、次のような案が示されました。
- マシンユーザーをOutside Collaboratorとして参加させる
- マシンユーザーからGitHub Appsに移行しInstallation Access Tokenを利用する
minimoではGHEC導入のタイミングで1番目を行いましたが、tokenを都度発行する2番目の方がセキュリティー的に望ましいので、順次切り替えていこうという話になりました。
CodeBuildの対応
上記の記事をベースに、CodeBuildでGitHub AppsのInstallation Access Tokenを使えるようにしました。
コード
#!/bin/bash -e
# get-github-token.sh
base64url() {
openssl enc -base64 -A | tr '+/' '-_' | tr -d '='
}
sign() {
openssl dgst -binary -sha256 -sign <(printf '%s' "${GITHUB_APP_PRIVATE_KEY}")
}
# 第1引数: GitHub Appsで扱うリポジトリのリスト (カンマ区切り)
IFS="," read -r -a repositories <<< "$1"
header="$(printf '{"alg":"RS256","typ":"JWT"}' | base64url)"
now="$(date '+%s')"
iat="$((now - 60))"
exp="$((now + (3 * 60)))"
payload="$(printf '{"iss":"%s","iat":%s,"exp":%s}' "${GITHUB_APP_ID}" "${iat}" "${exp}" | base64url)"
signature="$(printf '%s' "${header}.${payload}" | sign | base64url)"
jwt="${header}.${payload}.${signature}"
# installation_id取得は与えられたリポジトリ群の0番目のリポジトリで行う
# 参考: https://github.com/actions/create-github-app-token/blob/25cc3bdc279c3498f554da7599deab2213b272e6/lib/main.js#L160
installation_id="$(curl --location --silent --request GET \
--url "https://api.github.com/repos/<org名>/${repositories[0]}/installation" \
--header "Accept: application/vnd.github+json" \
--header "Authorization: Bearer ${jwt}" \
| jq -r '.id')"
quoted_repositories=()
for repository in "${repositories[@]}"; do
quoted_repositories+=("\"$repository\"")
done
# 与えられたリポジトリ群に対して有効なtoken取得
curl --location --silent --request POST \
--url "https://api.github.com/app/installations/${installation_id}/access_tokens" \
--header "Accept: application/vnd.github+json" \
--header "Authorization: Bearer ${jwt}" \
-d "{\"repositories\": [$(IFS=","; echo "${quoted_repositories[*]}")]}" \
| jq -r '.token'
#!/bin/bash -e
# CI/CDのスクリプト
# リポジトリ1とリポジトリ2に対する権限を持ったtoken
GITHUB_TOKEN="$(./get-github-token.sh <リポジトリ1>,<リポジトリ2>)"
export GITHUB_TOKEN
revokeGitHubToken() {
curl --location --silent --request DELETE \
--url "https://api.github.com/installation/token" \
--header "Accept: application/vnd.github+json" \
--header "Authorization: Bearer ${GITHUB_TOKEN}"
}
# 正常終了・異常終了を問わず、CI/CD終了時にGITHUB_TOKENを失効させる
trap revokeGitHubToken EXIT
# GITHUB_TOKENの使用例
git clone https://x-access-token:${GITHUB_TOKEN}@github.com/<org名>/<リポジトリ1>
なお、環境変数 GITHUB_APP_ID
, GITHUB_APP_PRIVATE_KEY
は次のようにSecret Managerから与えられている前提です。
# buildspec.ymlの一部
env:
secrets-manager:
GITHUB_APP_ID: "<GitHub AppsのIDのSecret名>"
GITHUB_APP_PRIVATE_KEY: "<GitHub Appsのprivate keyのSecret名>"
工夫したポイント
GitHub Actionsでは actions/create-github-app-token
を使う形でGitHub Appsに対応させたため、このActionsに近い使い勝手や挙動を意識しました。
カンマ区切りで権限を付与したいリポジトリ群を与えられるようにした
actions/create-github-app-token
では repositories
パラメータでカンマ区切りによるリポジトリの指定が可能なので、これに合わせました。
これを実現するにあたり、変数 repositories
への代入では次のようなことを行っています。
-
<<<
(ヒアストリング) を使って第1引数をファイルとして入力 -
IFS=","
で区切り文字を一時的にカンマに変更 -
read -r -a
を使って配列として代入
IFS="," read -r -a repositories <<< "$1"
正常終了・異常終了を問わず、CI/CD終了時に必ずtokenを失効させる
actions/create-github-app-token
でもCI/CD終了時に失効させています。
trap ... EXIT
を使うと正常・異常問わず、終了直前に処理を行うことができるので、これを使ってtokenを失効させるようにしました。
revokeGitHubToken() {
curl --location --silent --request DELETE \
--url "https://api.github.com/installation/token" \
--header "Accept: application/vnd.github+json" \
--header "Authorization: Bearer ${GITHUB_TOKEN}"
}
# 正常終了・異常終了を問わず、CI/CD終了時にGITHUB_TOKENを失効させる
trap revokeGitHubToken EXIT
最後に
MIXIやminimoでは一緒に働く仲間を募集中です!
リモート勤務も可能ですので、詳しくは下記採用ページをご確認ください。
Discussion