GitHub Apps ってマイナーなんですか?
안녕하신게라!パナソニック コネクト株式会社クラウドソリューション部の加賀です。
Qiitaで「GitHubApps」を検索してみると、想像以上に記事数が少ないことに気が付きました。
ネットですと「Personal Access Token(PAT)」を使う事例が出てきます。GitHub ActionsのCI/CDでGitHub Appsを認証に使う「サービスアカウントとしての活用」の記事、無いなら書きましょう、そうしましょう。
なぜ、GitHub Appsはこれほどまでに使われていないのでしょうか。
敬遠されているのはおそらく「GitHub Apps=外部SaaSとの連携ツール」というイメージが強すぎること、単なる文字列を発行するだけのPATと比べ、秘密鍵を用いたJWT(JSON Web Token)の署名から一時トークンへの交換処理など、認証フローの概念が複雑で導入ハードルが高く感じてしまうこと、気軽に試そうと思ってもOrganization管理者権限が必要になるのも要因と考えられます。
実際にCI/CDで別リポジトリを操作したりOrganizationレベルのAPIを叩いたりする場面では、PATではなくGitHub Appsをサービスアカウントとして使うのがエンタープライズでは主流になってきています1。
けど、いざActionsに組み込んで他リポジトリを操作しようとすると、権限エラーやスコープ起因の「見えない壁(403など)」でハマるケースも多発します。
本記事では「なぜPATではなくGitHub Appsに乗り換える必要があるのか?」の解説から、Organization環境でのサービスアカウントとしての仕組み・ハマリポイント、安全な組み込み・運用ベストプラクティスまで、点在しがちな知識をつなぎ合わせて実践的にまとめます。
なお、GitHub AppsにはWebhookの受信エンドポイントとしての側面もありますが、本記事ではトークン認証を使ったActions連携に絞って解説します。
GitHub Appsのメリットは?
PATとの比較でGitHub Appsのメリットを整理します。
GitHub Actionsで他のリポジトリを操作したいとき、手軽なのは開発者の誰かのPersonal Access Token(PAT)を使うことですが、PATは「特定個人のアカウント」に紐づきます。そのため、PATを発行した担当者が退職したりアカウントを消したりすると、突然CIパイプラインが動かなくなってしまうため、属人化のリスクがあります。
個人のPATは、APIの呼び出し上限が通常1時間に5,000回と決まっているため、活発な開発組織だとすぐに上限に達してCIパイプラインが止まる問題もあります。
最近はリポジトリ単位で権限を絞れる「Fine-grained PAT」も登場していますが、依然として特定個人のアカウントに紐づいてしまう(退職時のリスクやレートリミット統合の課題がある)こと、さらにOrganizationポリシーによってはFine-grained PATの利用に管理者承認が必要なケースがあり、CI/CDの自動化には不向きです。Organization規模の自動化ではGitHub Appsが最適解となります。
これらを解消するのが「GitHub Apps」です。GitHub Appsは特定個人のアカウントに依存しない「独立したサービスアカウント(システムアカウント)」のような扱い方ができます。そのため、誰かがチームを抜けてもCIは止まりませんし、必要なリポジトリにだけ最低限の権限を付与することができます。
APIの呼び出し回数上限においても、個人ユーザのPATのレートリミット枠とは別の独立したプールを持っており、GitHub Appsはその影響を受けません。GHEC環境ではさらに高い上限が設けられており、活発な組織でCIをガンガン回す際にも有利な仕様です2。
監査の観点からも有用です。GitHub AppsによるAPI操作はOrganizationの監査ログ(Audit Log)にApp名で記録されます。PATの場合は操作者の個人アカウント名で記録されるため、「自動化による操作」と「人間の手作業」を監査ログ上で区別しにくいという問題があります。GitHub Appsなら my-deploy-app[bot] のようにBOT名で一貫して記録されるため、セキュリティインシデント発生時の追跡や、コンプライアンス監査での操作証跡の確認が格段に容易になります。
以下に、PAT(Classic / Fine-grained)とGitHub Appsの主要な違いを比較表にまとめます。
| 観点 | Classic PAT | Fine-grained PAT | GitHub Apps |
|---|---|---|---|
| 属人化リスク | 高い(個人アカウントに紐づく) | 高い(個人アカウントに紐づく) | なし(Org所有の独立エンティティ) |
| 権限粒度 | リポジトリ単位の制御不可 | リポジトリ・権限単位で制御可能 | リポジトリ・権限単位で制御可能 |
| トークン有効期限 | 無期限設定可(カスタム期限も可) | 最大1年(Orgポリシーで短縮可) | 1時間(自動失効) |
| レートリミット | 5,000回/時(ユーザ枠を共有) | 5,000回/時(ユーザ枠を共有) | 5,000〜15,000回/時(独立枠) |
| 監査ログ | ユーザ名で記録 | ユーザ名で記録 | App名 [bot] で記録・識別容易 |
| 退職時の影響 | CIが停止するリスク | CIが停止するリスク | 影響なし |
| Org管理者の承認 | 不要(ただしSAML SSO有効Orgでは別途承認要) | Orgポリシーにより必要な場合あり | App作成時・権限変更時に承認が必要(通常運用は独立) |
一度移行してしまえば、担当者が入れ替わっても何も変わらなくなります。まだPATで運用しているチームは、ぜひ移行を検討してみてください。
認証フローの仕組みと組み込み方
本記事では、GitHub AppのOrganizationへの作成・インストール手順そのものは扱いません(最新の公式ドキュメント参照)。Appが作成済みの状態からActionsへの組み込みに焦点を当てます。
PATの場合は、Secretsに文字列を一つ貼り付けるだけでした。簡単ですが漏れたらオシマイです。
GitHub Appsを利用する場合、いくつか手順を踏む必要があります。仕組みとしては、まずAppの秘密鍵(Private Key)を使ってActionsランナー上でJWTをローカル生成し(有効期限は最大10分)、そのJWTをGitHubのAPIに送ることでInstallation Access Tokenを払い出してもらう、という2ステップの流れになります。Installation Access Tokenは発行から1時間で失効する使い捨てトークンのため、セキュアに操作を行うことができます。秘密鍵がSecretsに必要なのは、このランナー上でのJWT生成に使われるためです。
文字にすると複雑そうですが、現在は公式から actions/create-github-app-token という便利なアクションが提供されているため、中身の難しい通信部分はすべて任せることができます(ネットの過去記事だとこれがサードパーティ製actionsのため不安が残っていた)。さらに、このアクションはジョブ終了後にpost stepでトークンを自動でRevoke(無効化)してくれるため、自前でスクリプトを書くよりもセキュアです。なお、ランナーの強制終了やワークフロータイムアウトなど極端なケースではpost stepが実行されない場合がありますが、Installation Access Tokenは最長1時間で自動失効するためリスクは限定的です。
呼び出しに使うClient IDは公開情報なのでSecretsではなくVariables(構成変数)に置いておき、秘密鍵だけをSecretsから読み込ませるのが実運用のセオリーです。
steps:
- name: Generate GitHub App Token
id: app-token
uses: actions/create-github-app-token@v3
with:
owner: your-organization # Organization名を指定
repositories: target-repo # スコープを限定する(推奨)
client-id: ${{ vars.APP_CLIENT_ID }} # IDはVariablesから読み込む
private-key: ${{ secrets.APP_PRIVATE_KEY }} # Organization Secretsを推奨します
- name: Checkout target repository
uses: actions/checkout@v6
with:
repository: your-organization/target-repo
token: ${{ steps.app-token.outputs.token }}
persist-credentials: false # デフォルトのクレデンシャル保存を無効化
(※v3.1.0で app-id が非推奨化され client-id が推奨になりました。レガシーの app-id も引き続き動作しますが、v2.x系やv3.0.0を使用していた場合はv3.1.0以降へアップグレードした上で app-id から client-id への置き換えが推奨されます。GitHub上の設定画面には両方の値が表示されており、Client IDは文字列形式で、数字のApp IDと混同しないようご注意ください)
セルフホストランナーを利用している場合、actions/create-github-app-token のバージョンによってはActionsランナーの最新化が必要になることがあります。エラーが出た際は 公式リリースノート で必要なランナーバージョンを確認してアップデートしてください。
よくある落とし穴
GitHub Appsの認証を組み込んでも、最初はどうしてもエラーで弾かれがちです。実際にハマったり、現場でよく見るパターンを6つ挙げます。
1. Appの権限設定が足りない(あるいは多すぎる)
PRを作りたいなら「Pull Requests」、コードを読みたいなら「Contents」といった具合に、GitHub上のApp設定画面でやりたいことに合わせて権限を細かく設定する必要があります。とりあえず全権限(Read/Write)を付けたくなりますが、それは後々リスクになります。最小権限(Least Privilege)の原則を守りましょう。
2. 個人アカウント上にAppを作ってしまう
PATの属人化を防ぎたくてGitHub Appsを選んだのに、「個人のSettings(Developer settings)」から作成してOrganizationにインストールしてしまうと、作成者が退職した際に鍵のローテーションや設定変更ができなくなります。作成は必ずOrganizationのSettings(Developer settings)から行いましょう。なお、誤って個人アカウントで作成してしまった既存のAppは、GitHubのTransfer ownership機能でOrganizationへ所有権を移転することも可能ですが、移転作業には確認フローが伴うため、最初からOrganizationで作成するのが最善です。
3. 操作したいリポジトリへのインストール漏れ
Appは作っただけでは動かず、Organizationにインストールした上で対象リポジトリへのアクセスも許可する必要があります。「GitHub Actionsを実行するリポジトリ」だけでなく、「チェックアウト・操作をしたい別のリポジトリ」にもインストール・許可が必要という点は、実際に詰まるまで気づきにくい落とし穴です。逆に「面倒だからAll repositories(全リポジトリ)にインストールしよう」とすると必要以上に広いアクセス権を与えてしまうため避けてください。アクセスを許可するリポジトリは必要最小限に絞ることが重要です。
4. トークンのスコープ指定ミス
actions/create-github-app-token でトークンを発行すると、デフォルトでは「今Actionsを動かしているリポジトリ」の権限しか持ちません。他のリポジトリも触りたいなら repositories: パラメータで明示的に指定する必要があります。repositories: を省略して owner: のみにすると、Appがインストールされている全リポジトリを操作できるトークンになってしまいます(All repositoriesでインストール済みだと影響が特に大きい)。本当に必要なリポジトリ名だけを列挙するようにしましょう。
なお、v2.2.0以降では permission-<name> パラメータ(例: permission-issues: write)でトークン発行時に権限をさらに絞り込むことも可能です。App設定画面での権限に加え、発行時にも最小限に制限する二重の防御が実現できます。
5. トークンがジョブをまたげない
actions/create-github-app-token はジョブ終了時のpost stepでトークンを自動Revokeします。そのため、発行したトークンを別ジョブに needs: で渡しても、到着時にはすでに無効化されているという落とし穴があります。複数ジョブで操作が必要な場合は、各ジョブ内でそれぞれトークンを発行する構成にしましょう。
6. Appの権限変更にはOrg管理者の承認が必要
GitHub Appの権限をあとから追加・変更した場合、既存のインストール先には即座に反映されません。Organizationの管理者(Owner)が新しい権限リクエストを承認するまで、変更前の権限のままで動作し続けます。この承認待ちの間にCIで新しい権限を前提とした処理を実行すると、権限不足で403エラーが発生します。権限変更後は必ずOrganizationのSettings → GitHub Apps → 該当Appで承認操作を行い、CIを再実行してください。
秘密鍵の管理と運用ベストプラクティス
GitHub Appsに乗り換えると次の課題は秘密鍵の管理です。複数のリポジトリで同じAppを使いたいからといって、各リポジトリのSecretsに秘密鍵をそれぞれ登録して回るのは避けたいところです。
そこで活用したいのが「Organization Secrets」です。Organization全体で秘密鍵を一元管理できる仕組みで、ここに一度だけ秘密鍵を登録しておき、設定画面から「この秘密鍵を利用できるリポジトリ(Selected repositories)」を絞ります。リポジトリごとに鍵をコピーする手間がなくなりますし、開発者に秘密鍵を直接渡す必要もなくなります。
また、トークン発行ジョブ自体の permissions: ブロックも最小限に絞ることを推奨します。actions/create-github-app-token はApp秘密鍵で独自に認証するため、GITHUB_TOKEN の権限は不要です。トークン発行は actions/checkout など GITHUB_TOKEN を使うステップと同一ジョブに混在させず、専用ジョブに分離した上で permissions: {} を設定することで GITHUB_TOKEN に余計な権限を持たせないようにでき、多層防御の観点からセキュリティをさらに高められます。
秘密鍵は定期的にローテーションするのを忘れずに。GitHubのApp設定画面で新しい秘密鍵を追加・ダウンロードし、Organization SecretsやVaultなどの保管先を更新してから古い鍵を削除する、という「追加→更新→削除」の順序で行えばダウンタイムなく安全にローテーションできます。なお、漏洩が疑われる場合は新鍵への更新を待たず旧鍵を即座に削除してください。
ただし、巨大な権限を持った1つのAppを全リポジトリで使い回すのはリスクが高いため、「フロントエンドデプロイ用のApp」「Terraform実行用のApp」など、目的別に小さなAppを複数作って使い分けるのも、被害を最小限に抑える上で欠かせない考え方です。
Reusable Workflowsでセキュリティ統制を強化する
Organization Secretsと合わせて「Reusable Workflows(再利用可能なワークフロー)」を活用すると、組織としてのセキュリティ統制をさらに強化できます。
トークン発行や他リポジトリのチェックアウトといった処理をReusable Workflowsにまとめておくことで、各チームが独自のCIファイルで自由にトークンを作れる状態を防ぎ、不必要に広い権限を要求する事故のリスクを下げられます。
Reusable Workflowsでは GITHUB_TOKEN の権限が呼び出し元(Caller)の permissions: 設定以下に制約されます(チェーンの中で権限を昇格させることはできません)。また、「Organization Secretsが利用できるリポジトリ」の許可リストに呼び出し元リポジトリを含めた上で、呼び出し元のワークフロー定義で secrets: inherit を明示するか、必要なシークレットを個別に渡す必要があります。この設定が抜けると再利用ワークフロー内でOrganization Secretsが参照できずエラーになるため、忘れずに確認してください。
おわりに
「なんか面倒だしPATでいいか」ってなってしまう気持ちは正直わかります。けどこの仕組みが腑に落ちると感想が変わります。毎回新しいトークンが払い出されてジョブ終了後に即Revokeされる設計は、長期間使い回すPATとは安心感が段違いです。Organization SecretsやReusable Workflowsとセットで整備すれば、個別リポジトリのCI担当者がいちいちセキュリティを意識しなくて済む状態に持っていけます。
PAT管理で地味に消耗してきたチームに、少しでも役立てば嬉しいです。
お断り
記事内容は個人の見解であり、所属組織の立場や戦略・意見を代表するものではありません。
あくまでエンジニアとしての経験や考えを発信していますので、ご了承ください。
-
自リポジトリ内の操作やパッケージ作成だけであれば、デフォルトで付与される
GITHUB_TOKENで足ります。ただしGITHUB_TOKENによるpushや PR 作成では、再帰的なワークフロー実行を防ぐGitHubの仕様により後続ワークフロー(on: pushなど)がトリガーされません(workflow_dispatchとrepository_dispatchは例外)。CIをチェーン構成にしたい場合もGitHub Appsへの乗り換えが有効です。なおGITHUB_TOKEN自体も内部的にはGitHub App Installation Access Tokenとして実装されています。 ↩ -
Installation Access Tokenのレートリミット詳細:非GHEC環境はベース5,000回/時からスタートします。リポジトリ数が20を超えるインストールでは各リポジトリあたり+50回/時、ユーザ数が20を超えるOrganizationでは各ユーザあたり+50回/時が加算され、上限は12,500回/時です。GHEC環境のOrganizationは一律15,000回/時の固定です。詳細な計算ルールはGitHub公式ドキュメントを参照してください。 ↩