はじめに
atama plusの平山です。
この記事は atama plus Advent Calendar 2023 の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 によるとGitにはLocal、HTTP、SSH、Gitの4種類の通信プロトコルが存在します。このうち認証の仕組みが存在するのはHTTPとSSHです。
Localプロトコル
Localプロトコルはディスク上の別のディレクトリに置かれたリモートリポジトリと通信するための方式です。主なユースケースはNFSのような分散ファイルシステムを使い、リモートサーバーのリモートリポジトリをローカルのファイルシステムにマウントしている場合です。ローカルのファイルシステムへのアクセスなので、このプロトコルには認証の仕組み自体がありません。
HTTPプロトコル
名前の通りHTTPを利用した通信方式です。HTTPの認証の仕組みに乗れるので、ユーザー名とパスワードを使ったBasic認証やBearerトークンを使ったBearer認証などが利用できます。
Basic認証
ユーザー名とパスワードを使った認証方式です。
Basic認証はHTTPのAuthorizationヘッダーに、ユーザー名とパスワードからなるクレデンシャルをセットしてリクエストを送信します。
ユーザー名とパスワードはコロンでつなげてBase64でエンコードします。
Aurhorization: Basic CREDENTIAL
また、URLに直接クレデンシャルを埋め込む方法もあります。
この場合、URLのドメインの前にユーザー名とパスワードを username:password@
の形式で埋め込みます。
https://username:password@www.example.com/
Basic認証の注意点として、HTTP自体はリクエストを暗号化せずに送信するので、クレデンシャルの盗聴を防ぐためにはHTTPSを利用する必要があります。
Bearer認証
トークンを利用した認証方式です。
Bearer認証はHTTPのAuthorizationヘッダーに、Bearerトークンをセットしてリクエストを送信します。
Authorization: Bearer TOKEN
SSHプロトコル
名前の通りSSHを利用した通信方式です。あらかじめサーバーにSSHの公開鍵を登録しておき、ローカルの秘密鍵を使ってサーバーに接続します。SSHを使っているので通信は暗号化されます。
複数の鍵を使い分ける場合は、SSHの設定でホスト毎に鍵を指定したり、Gitの core.sshCommand 設定でリポジトリ毎に鍵を指定することができます。
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の使用が推奨されています。
GitHubのREST APIには、まだFine-grained personal access tokenに対応していないエンドポイントがあります。利用したい場合はclassic版のトークンを使うしかないようです。
対応済みのエンドポイント一覧は Endpoints available for fine-grained personal access tokens で確認できます。
GitHub Apps
GitHub AppsはGitHubの機能を拡張できる仕組みです。 GitHub Marketplace で公開されたGitHub Appを利用したり、自分でGitHub Appを作ったりできます。
GitHub Appはリポジトリに対してインストールすることで、インストールされたリポジトリに対するGit操作やAPI処理を実行させることができます。GitHub Appの用途によって必要な権限は細かく設定できます。例えば、リポジトリの読み取り権限を持つGitHub Appは、リポジトリにインストール後、インストールされたリポジトリの内容を読み取ることができるようになります。
GitHub Appをユーザーに紐づかないBotとして利用する場合は、インストールとして認証し、ユーザーとして何らかの処理をさせたい場合はユーザーに代わって認証します。
インストールとして認証する場合の大まかな流れです。
- GitHub Appの秘密鍵を使いJWTを生成する
- JWTをBearerトークンとして使い、REST APIでインストールアクセストークンを生成する
- インストールアクセストークンをBearerトークンとして使い、GitやREST APIを操作する
- (使い終わったインストールアクセストークンを失効させる)
GitHub Appの秘密鍵は、GitHub Appの設定画面から発行できます。秘密鍵には有効期限がないので、必要な場合は手動で削除します。秘密鍵は複数発行でき、一時的に新旧の秘密鍵を発行してローテーションすることができます。
インストールアクセストークンは有効期限が最長で1時間の短命なトークンです。CIから使う用途だと、ジョブの中でインストールアクセストークンを発行し、処理が終了したらトークンは使い捨てます。有効期限が短いので、使い終わったインストールアクセストークンはそのまま放置でも良いですが、REST APIで明示的に失効させることもできます。
インストールアクセストークンは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つのリポジトリだけなので、複数のリポジトリを操作したい場合は、リポジトリの数だけ鍵を発行し管理する必要があります。
デプロイキーを登録したユーザーをリポジトリから削除しても、デプロイキー自体は有効なままです。リポジトリから削除したユーザーがデプロイキーを持っている場合、引き続きリポジトリにアクセス可能なため注意が必要です。
おわりに
GitHubの認証の仕組みは種類が多く、全体像が分かりにくいのですが、基本となるHTTPのBearer認証とSSHによる認証の2種類を理解すると、応用が効きやすいと思います。
GitHub Appsは学習ハードルが高いものの、ユーザーアカウントに依存せず自動処理ができたり、必要な権限を細かく設定できたりと、自動処理を運用する上でのメリットが多いです。一方でセットアップする上での手間もそれなりにかかるので、要件に合わせてよりシンプルなPersonal Access Tokenやデプロイキーも検討できると良いと思います。
明日は @igawy さんによる 「社内ギルド活動を通じて学んだこと」です。お楽しみに!