Personal Access Token以外の選択肢を増やすためにGitHubの認証を基礎から調べてみた


# はじめに

atama plusの平山です。
この記事は [atama plus Advent Calendar 2023](https://qiita.com/advent-calendar/2023/atamaplus) の19日目になります。

GitHub x CIで凝ったことをやろうとして、CIに許可された権限だけでは足りなかった経験は無いでしょうか？
- REST APIを操作したい
- 自動で変更のコミットを作りたい
- 別のprivateリポジトリのコードも使いたい

このような場合に安直に権限不足を解消しようとすると、マシンアカウントのPersonal Access Tokenを発行して使うのが分かりやすく手間も少ないです。社内でもPersonal Access TokenよりGitHub Appsの利用が推奨されているものの、GitHub Appsの学習ハードルが高いこともあり、まだまだPersonal Access Tokenを利用している箇所が多いです。

しかし、Personal Access Tokenを使った方法はマシンアカウントのためにGitHub Organizationのシートを消費してしまうなどのデメリットもあり、他の方法で認証した方が良い場合もあります。

最近、業務でGitHubのprivateリポジトリの連携を設計する機会があったので、今までなんとなくの理解で使っていたGitHubの認証の仕組みを改めて調べてみました。

この記事ではGitHub AppsやデプロイキーといったGitHubの機能を正しく利用するために、GitHubの認証について調査した結果をまとめます。

# Gitの認証

まず、GitHubのベースとなっているGit本体の通信や認証の仕組みについて調べてみました。

[git-scm.com](https://git-scm.com/book/ja/v2/Git%E3%82%B5%E3%83%BC%E3%83%90%E3%83%BC-%E3%83%97%E3%83%AD%E3%83%88%E3%82%B3%E3%83%AB) によるとGitにはLocal、HTTP、SSH、Gitの4種類の通信プロトコルが存在します。このうち認証の仕組みが存在するのはHTTPとSSHです。

## Localプロトコル

Localプロトコルはディスク上の別のディレクトリに置かれたリモートリポジトリと通信するための方式です。主なユースケースはNFSのような分散ファイルシステムを使い、リモートサーバーのリモートリポジトリをローカルのファイルシステムにマウントしている場合です。ローカルのファイルシステムへのアクセスなので、このプロトコルには認証の仕組み自体がありません。

## HTTPプロトコル

名前の通りHTTPを利用した通信方式です。[HTTPの認証](https://developer.mozilla.org/ja/docs/Web/HTTP/Authentication)の仕組みに乗れるので、ユーザー名とパスワードを使ったBasic認証やBearerトークンを使ったBearer認証などが利用できます。

### Basic認証

ユーザー名とパスワードを使った認証方式です。

Basic認証はHTTPのAuthorizationヘッダーに、ユーザー名とパスワードからなるクレデンシャルをセットしてリクエストを送信します。
ユーザー名とパスワードはコロンでつなげてBase64でエンコードします。
`Aurhorization: Basic CREDENTIAL`

また、URLに直接クレデンシャルを埋め込む方法もあります。
この場合、URLのドメインの前にユーザー名とパスワードを `username:password@` の形式で埋め込みます。
`https://username:password@www.example.com/`

:::note warn
Basic認証の注意点として、HTTP自体はリクエストを暗号化せずに送信するので、クレデンシャルの盗聴を防ぐためにはHTTPSを利用する必要があります。
:::

### Bearer認証

トークンを利用した認証方式です。

Bearer認証はHTTPのAuthorizationヘッダーに、Bearerトークンをセットしてリクエストを送信します。
`Authorization: Bearer TOKEN`

## SSHプロトコル

名前の通りSSHを利用した通信方式です。あらかじめサーバーにSSHの公開鍵を登録しておき、ローカルの秘密鍵を使ってサーバーに接続します。SSHを使っているので通信は暗号化されます。

複数の鍵を使い分ける場合は、SSHの設定でホスト毎に鍵を指定したり、Gitの [core.sshCommand](https://git-scm.com/docs/git-config#Documentation/git-config.txt-coresshCommand) 設定でリポジトリ毎に鍵を指定することができます。

## Gitプロトコル

Gitプロトコルは専用のポート(9418)を使ってリモートリポジトリと通信する方式です。Gitプロトコルには認証の仕組みが存在しない代わりに、高速な通信が可能です。主なユースケースは公開リポジトリで大量の読み取り専用アクセスを処理する場合です。

# GitHubの認証

続いて本命のGitHubで利用できる認証の仕組みを調べてみました。CIからの利用を想定して調査したのでブラウザやデスクトップアプリを使った認証は省略します。

大きく分けるとGitの認証で取り上げたHTTPによる認証とSSHによる認証が存在します。

## HTTPによる認証

2023年現在、GitHubのHTTPによる認証は、基本的にトークンを使ったBearer認証になります。以前はユーザー名とパスワードを使ったBasic認証も利用できたのですが、2021年に廃止されました。

GitHubの認証の仕組みによってトークンを発行する方法が変わります。

### Personal Access Token

ユーザーアカウントに紐づいたトークンで、トークンを発行したユーザーとして認証できます。
トークンはGitHubの設定画面から発行して使います。トークンを発行する際に実行可能な権限のスコープを設定することができます。

トークンにはPersonal access token (classic)とBeta版のFine-grained personal access tokenの2種類があります。

Fine-grained personal access tokenはclassic版より細かな権限の設定が可能になり、トークンの有効期限が必須になるなど、セキュリティが強化されています。GitHubのドキュメントではFine-grained personal access tokenの使用が推奨されています。

:::note warn
GitHubのREST APIには、まだFine-grained personal access tokenに対応していないエンドポイントがあります。利用したい場合はclassic版のトークンを使うしかないようです。
対応済みのエンドポイント一覧は [Endpoints available for fine-grained personal access tokens](https://docs.github.com/en/rest/overview/endpoints-available-for-fine-grained-personal-access-tokens?apiVersion=2022-11-28) で確認できます。
:::

### GitHub Apps

GitHub AppsはGitHubの機能を拡張できる仕組みです。 [GitHub Marketplace](https://github.com/marketplace) で公開されたGitHub Appを利用したり、自分でGitHub Appを作ったりできます。

GitHub Appはリポジトリに対してインストールすることで、インストールされたリポジトリに対するGit操作やAPI処理を実行させることができます。GitHub Appの用途によって必要な権限は細かく設定できます。例えば、リポジトリの読み取り権限を持つGitHub Appは、リポジトリにインストール後、インストールされたリポジトリの内容を読み取ることができるようになります。

GitHub Appをユーザーに紐づかないBotとして利用する場合は、**インストールとして認証**し、ユーザーとして何らかの処理をさせたい場合は**ユーザーに代わって認証**します。

インストールとして認証する場合の大まかな流れです。
- GitHub Appの秘密鍵を使い[JWTを生成する](https://docs.github.com/ja/apps/creating-github-apps/authenticating-with-a-github-app/generating-a-json-web-token-jwt-for-a-github-app)
- JWTをBearerトークンとして使い、REST APIで[インストールアクセストークンを生成する](https://docs.github.com/ja/apps/creating-github-apps/authenticating-with-a-github-app/generating-an-installation-access-token-for-a-github-app)
- インストールアクセストークンをBearerトークンとして使い、GitやREST APIを操作する
- (使い終わったインストールアクセストークンを失効させる)

GitHub Appの秘密鍵は、GitHub Appの設定画面から発行できます。秘密鍵には有効期限がないので、必要な場合は手動で削除します。秘密鍵は複数発行でき、一時的に新旧の秘密鍵を発行してローテーションすることができます。

インストールアクセストークンは有効期限が最長で1時間の短命なトークンです。CIから使う用途だと、ジョブの中でインストールアクセストークンを発行し、処理が終了したらトークンは使い捨てます。有効期限が短いので、使い終わったインストールアクセストークンはそのまま放置でも良いですが、REST APIで明示的に失効させることもできます。

:::note info
インストールアクセストークンはAuthorizationヘッダーにセットする以外に、Basic認証のパスワードとしてURLに埋め込んでも利用できるようです。ユーザー名には `x-access-token` を設定します。
`https://x-access-token:TOKEN@github.com/owner/repo.git`
:::

ユーザーに代わって認証する方法は、今回のユースケースでは利用せず、詳しく調査しなかったので具体的な方法は省略します。

### OAuth Apps

GitHub Appsと似た機能ですが、リポジトリではなくユーザーアカウントに対してインストールして使うのがOAuth Appsです。OAuth Appをインストールしたユーザーアカウントをリポジトリから削除すると、動かなくなってしまったりすることから、チーム開発ではリポジトリに対してインストールできるGitHub Appsの方が適しているようでした。GitHub Appsでもユーザーとしての操作ができることから、GitHubのドキュメントでもOAuth AppsよりGitHub Appsの方が推奨されているようです。

## SSHによる認証

ユーザーアカウントのSSHキーとデプロイキーの2種類の方法があります。

### ユーザーアカウントのSSHキー

SSHの鍵を使い、ユーザーアカウントとして認証する方式です。
あらかじめGitHubの設定画面でユーザーアカウントにSSHの公開鍵を登録しておき、SSHの秘密鍵を使ってユーザーアカウントとして認証します。
認証したユーザーに許可されたアクションは全て実行でき、権限を絞るといったことはできません。

### デプロイキー

デプロイキーもSSHの鍵を使い認証する方式ですが、ユーザーアカウントではなく、リポジトリに対して公開鍵を登録します。
デフォルトの権限は対象のリポジトリの読み込み専用となります。必要な場合は書き込み権限を付与することもできます。

1つのデプロイキーを登録できるのは1つのリポジトリだけなので、複数のリポジトリを操作したい場合は、リポジトリの数だけ鍵を発行し管理する必要があります。

:::note warn
デプロイキーを登録したユーザーをリポジトリから削除しても、デプロイキー自体は有効なままです。リポジトリから削除したユーザーがデプロイキーを持っている場合、引き続きリポジトリにアクセス可能なため注意が必要です。
:::

# おわりに

GitHubの認証の仕組みは種類が多く、全体像が分かりにくいのですが、基本となるHTTPのBearer認証とSSHによる認証の2種類を理解すると、応用が効きやすいと思います。

GitHub Appsは学習ハードルが高いものの、ユーザーアカウントに依存せず自動処理ができたり、必要な権限を細かく設定できたりと、自動処理を運用する上でのメリットが多いです。一方でセットアップする上での手間もそれなりにかかるので、要件に合わせてよりシンプルなPersonal Access Tokenやデプロイキーも検討できると良いと思います。

明日は @igawy さんによる 「社内ギルド活動を通じて学んだこと」です。お楽しみに！