GitHubで扱うPersonal access tokenの利用方法をセキュアにする

GitHubで扱うPersonal access tokenの利用方法をセキュアにするのタイトル画像

こんにちは、セキュリティチームの@sota1235です。

セキュリティチームでは昨年の夏頃からGitHub上のセキュリティリスクを洗い出し、順に対応や改善を行っています。

そのうちの1つとして、昨年の秋ごろからGitHubのPersonal Access Tokenの取り扱いの改善を行ってきました。

具体的には以下の取り組みを行いました。

  • CI等で利用されているPersonal Access Tokenの利用廃止
  • OrganizationにおけるPersonal Access Token(classic)の利用禁止設定

今回はこの2つの取り組みについて、どのような課題設定を行い、どんな手順で完了したのかをお話しします。

以下のような課題感、疑問をお持ちの方に対する1つの回答になりうると思うので該当する方はぜひご一読ください🙏

  • GitHubにおけるPersonal Access Tokenとどう付き合うべきかの解像度が低い
  • Personal Access Tokenにまつわるセキュリティ上の対策が知りたい

前提知識: Personal Access Tokenとは

Personal Access Tokenとは、その名の通りGitHubの個人アカウントにひもづくAccess Tokenを指します。

docs.github.com

Access TokenなのでよくあるユースケースとしてはGitHub APIへのアクセス時の利用が挙げられます。何かの自動化の際のプログラムで利用したり、IDE等のアプリケーションとGitHubを連携する際等に利用したことがある方も多いのではないでしょうか。

Personal Access Tokenには種類がある

GitHubでは以下の2種類のPersonal Access Tokenがあります。

詳しい定義は各リンク先の公式ドキュメントを参照してもらえればと思いますが、主な相違点として以下が挙げられます。

  • 設定できる権限の細かさ
    • 例えばFine-grained personal access tokensではリポジトリ単位での権限設定が可能ですがPersonal access tokens(classic)ではできません
  • Organizationによる中央集権的な管理が可能かどうか
    • Fine-grained personal access tokensはOrganization ownerによる管理が可能です
    • ひもづくアカウント、設定された権限、有効期限が管理者によって確認可能です
    • また、revokeも可能です

この記事でも触れていきますが、セキュリティ観点ではFine-grained personal access tokensに移行していくことがbetterな選択肢となります。

本記事での表記について

表記が長くなるのでこの記事では以後、下記のように記載します。

  • Personal Access Token
    • PAT
  • Personal access token(classic)
    • PAT(classic)
  • fine-grained personal access token
    • PAT(fine-grained token)

では本題となる、具体的にどのような取り組みをしてきたのかについてお話しします。

課題設定

当時の状況

まずは当時、取り組みを始める前の10Xの状態に軽く触れます。

10Xでは開発にGitHubを利用しており、業務効率化やCI/CDとしてGitHub Actionsを利用しています。

その利用にあたり以下のような状態がありました。

  • GitHub Actionsのworkflow上でGitHub APIへのアクセスやgit commitを行う際にPATを利用していた
  • PATは誰がいつ、どのような権限で設定していたのか記録が残っておらず動作状態から推測するしかなかった
  • PATの利用方法に関するガイドラインは無く、会社全体での管理は誰もしていない状態だった
  • PAT(classic), PAT(fine-grained token)のどちらを利用しているかは個々人の判断で決まっていた

誤解を恐れずに言えば、スピードを優先して事業に集中するフェーズのベンチャーではよく見かける状態でもあるのかなと個人的には思います。

何が課題なのか

この状態をセキュリティ観点で見た時にどのような課題があるかを整理します。

PATの共有に伴うリスク

PATはPersonalという名の通り個人に紐づくcredential情報であり、設定された権限に基づいてその個人として振る舞うことができます。

ゆえにその管理レベルはパスワードと同レベルとすべきであり、個人が管理すべき機密情報です。PATを知り、利用できるのは発行したアカウントの持ち主のみであるべきで、持ち主以外への共有はすべきではありません。

これに関してはGitHubのドキュメントで言及しています。

docs.github.com

PATの管理不備に伴うリスク

PATは共有されるべきではない、と言ったものの現実的にはどうしても利用しないといけない場面があります。

例えばとあるSaaSとGitHubを業務上連携しないといけないが、GitHub AppsやOAuth appによる連携に対応していないケースです。

これらの場合は得られるリターン(業務効率等)とリスクを天秤にかけて、利用を許容するかどうかを判断します。

その際には以下を管理すべきです。

  • そのPATのライフサイクルの管理(利用開始からrevokeまでのサイクル、ローテーション等)
  • 権限が適切かどうかの管理
  • そのPATがどこに存在し、誰が利用しうる状態なのかを把握する

この管理が不十分だと利用していない権限の強いPATがいつの間にか漏洩してしまったり、ローテーションをしたくてもどこで何をすべきかが分からなくなります。

また、PAT(classic)の場合はOrganizationに対して権限を持つものを中央集権的に管理できないという問題があり現状としてどれくらいのPAT(classic)が存在し、それぞれがどんな有効期限、権限を持っており、activeなのかnon-activeなのかを調べる方法が実質的にありませんでした。

PAT(classic)を利用することによる権限過多

PATはいずれの種類も権限を設定することができます。この設定は最小権限の原則に従い行うのが望ましいです。

しかしPAT(classic)では権限を絞る際の柔軟性がPAT(fine-grained)と比べると乏しく、どうしても権限過多になってしまうケースがほとんどでした。

PAT(classic)でできてPAT(fine-grained)でできる具体的な権限設定の例をいくつか挙げると以下です。

  • RepositoryのIssueやPull Requestだけに権限を付与したい
  • 特定のRepositoryだけに権限をつけたい

必要以上の権限は漏洩時のリスクを高めることももちろんですが、すでに設定されたPATを当初の目的とは違う形で流用されやすい、というデメリットもあります。実際に10Xでは設定されてたPAT(classic)の権限が強く、ほとんど何でもできてしまうためにあらゆる箇所で参照されるという状態になっていました。

設定できる権限についてもっと知りたいという方はぜひご自身の設定ページ(https://github.com/settings/tokens)でTokenを作る画面を開いて確認してみてください。

どう言う状態を目指すべきか

これらのリスクを踏まえ、以下の状態を目指すこととしました。

  1. PATの利用を最小化する
  2. PAT(classic)の利用を廃止し、PAT(fine-grained)の利用を原則とする

具体的に何をやったのか説明していきます。

PATの利用を最小化する

要対応項目の洗い出し

これに取り組むためにはまず、現状の利用状況を把握する必要があります。

10Xの場合はGitHub ActionsのSecretsに設定されて利用してるケースがほとんどだったのでGitHub APIからSecret名を取得、怪しい名前のものを1つ1つ確認していき対応項目を洗い出しました。

書き捨てのscriptなんで改善の余地しかないですが、その際の手順を一応書いておきます。

※ この方法ではenvironment secretsの洗い出しはできません。当時の10Xは利用が進んでいなかったので洗い出してませんが、利用してる場合は洗い出しの順序にもう一捻り必要です

1. ActiveなOrganization repository一覧を取得する

ghコマンドが便利です。

gh repo list your_org_name -L 1000 --no-archived --json name > repo.json

2. 1で出力したrepoのsecretsを順番に書き出す

下記のファイルを保存して node secret_list.js >> secrets.txtみたいな感じでやりました。

const repoJson = require('./repo.json');
const { execSync } = require('child_process');

for (const { name } of repoJson) {
  console.log(`progress for your_org_name/${name} started...`);
  execSync(`echo '【your_org_name/${name}】' >> secrets.txt`);
  execSync(`gh secret list --repo your_org_name/${name} >> secrets.txt`);
  execSync('echo "" >> secrets.txt'); // change line
  console.log(`progress for your_org_name/${name} finished!`);
}

書いてて気づきましたがstep 1もこのJSに混ぜちゃえばいいと思います。

洗い出し

そんなこんなで一覧を出力したのであとは怪しそうなsecretの目星をつける -> 利用方法を見てPATかどうか判断する、というのを行いました。

結果の一部、ほぼモザイク

PATは大体 PATとか TOKENとかの文字列が入ってるので割とわかりやすいなと思いました。また、これは副次的な成果だったんですがこの洗い出しの過程でそもそもすでに参照されてないsecretsも多く見つけたため棚卸しにもつながりました。

PATを置き換える

PATを置き換える方法は主に2つあります。

  • github.tokenを利用する
  • GitHub Apps installation access tokenを利用する

github.tokenで事足りるケース

そもそもPATがいらなかったパターンです。このケースは意外と多く、github.tokenでユースケースを満たせないと勘違いしてPATが利用されてるケース。それをコピペして利用箇所が増えてしまったケースが見受けられました。

こちらに関してはとてもシンプルで、適切なpermissionを指定し、利用するように修正するだけです。

公式ドキュメントで詳しく説明してるので、こちらを参照するのが良いです。

docs.github.com

また、適切なpermissionを設定する取り組みに関しては過去に記事を書いたのでこちらも参考にしてみてください。

product.10x.co.jp

GitHub Apps installation access tokenが必要なケース

こちらは言い換えるとgithub.tokenではユースケースを実現できないパターンです。あるあるなものとしては以下が挙げられます。

  • Organization内の他のrepositoryのコードを参照したい
  • 自動でPull Requestを生成し、それに対してCIを実行してほしい

他にも多くのユースケースがありますが、前者はそのRepositoryの外側の権限が必要なケースと言えるでしょう。

後者は知ってる人は知ってる仕様だと思いますが、GitHub Actionsでは無限ループを抑制するためにgithub.tokenを用いて作られたPull Requestに対してはworkflowが実行されません。この仕様は防御機構としてありがたい一方、Pull Requestを自動生成してデプロイフローを組みたいようなケースでは回避する必要があり、その際にはgithub.token以外の有効なTokenが必要になります。

github.com

これに関してはシンプルに以下の手順で置き換えを行いました。

  • 利用されてるPATに必要な最小権限を洗い出し、その権限を持つGitHub Appを作成する
  • 該当のGitHub AppをOrganizationにinstallし、適切な権限スコープを設定する(repository等)
  • 上記のPrivate Keyを発行、Actionsから参照できる状態にしGitHub Apps installation access tokenを発行する

tokenの発行にはAPIのアクセス等が必要なんですが、その辺のロジックをまるっとwrapしてくれるlibraryがいくつかあるので利用を検討すると良いでしょう。

公式からも出ているため、今はこれの利用が無難だと思います。

github.com

10Xの場合はinstallation access tokenを発行する際にrepository選択によるスコープの制限を強制したい、というニーズがあったので内製したものを利用しています。

この置き換えにより以下の2点を満たせる状態になりました。

  • 個人に紐付けず、Organizationの管理下で設定した権限のTokenを利用できる
  • 万が一、Tokenが漏洩しても生存期間が短いため被害範囲を大きく縮小できる

余談: Private keyが漏洩したら

直前に達成したこととして万が一、Tokenが漏洩しても生存期間が短いため被害範囲を大きく縮小できると書きましたが、一方で発行したPrivate KeyとApp IDが漏洩してしまったらTokenを発行できる状態になるため、権限が永続化できてしまうというリスクは変わりません。

これに関しては10XではCloud Key ManagementにPrivate keyを格納し、原理的に読み出しをできなくすることで漏洩のリスクを最小化することを検討しています。

これに関しては公式ドキュメントでも言及があり、利用するCloud Platformの同等のソリューションを利用して管理することを検討すると良いでしょう。

docs.github.com

これが実現できると以下のメリットがあると考えています。

  • 最初の設定後のKeyの破棄を徹底できれば、Cloud事業者側からの漏洩以外の経路を潰せるのでローテーションの必然性を薄めることができ、運用コストを下げられる
  • (Google Cloudの場合)Private Keyへのアクセスをworkload identity連携で行うことにより複数のrepositoryから使いたい等のユースケースでActions secret, environment secretsを量産することなく運用することができる

これに関してはまた別記事で解説できればと思っています。

PAT(classic)の利用を廃止し、PAT(fine-grained)の利用を原則とする

PAT(classic)の利用はOrganizationの設定で抑制することができます。

※ 個人アカウントでの利用は可能です、スコープはあくまでOrganizationへのアクセス権限を持つPAT(classic)

docs.github.com

これを行うことにより以下のリスクを低減することができます。

  • PATの管理不備に伴うリスク
  • PAT(classic)を利用することによる権限過多

特に前者で受けられる恩恵は大きく、個人が発行したPAT(fine-grained)であってもOrganizationに対して権限を持つものはOwnerの管理画面から参照することができ、必要に応じてrevoke等の処理が行えます。

また、Audit logもわかりやすく残るため有事の際の初期対応、事後の調査において大きなメリットがあります。

一方で何も考えずに利用を廃止すると意図せぬハレーションが起きるため、いくつかのステップを踏んで施策を進めました。

把握してる範囲でのPAT(classic)を廃止、もしくはPAT(fine-grained)に置き換え

当然ですがPAT(classic)を使ってる場所があるのに無効化をしたらその利用箇所のシステムや業務が止まります。そのため、可能な範囲で調査を行い置き換えや廃止作業を行なっていきました。

GitHub Container RegistryからArtifact Registryへの移行

PAT(fine-grained)ではできず、PAT(classic)でしかできない唯一のものとしてGitHub Container Registryへの認証があります。

docs.github.com

社内では一部でGitHub Container Registryからlocal環境にimageをpullする業務フローがあり、その際には個々人がPAT(classic)の発行を行ってました。

そのPAT(classic)を使わずに済むよう、利用していたimageを全てGoogle CloudのArtifact Registryに移行しました。

基本的にメンバーがlocal環境で利用するimageがgcloudコマンドで適切に認証すれば使えるような状態になっています。

ポリシー策定、アナウンスと無効化の実行

これはやるだけではあるものの、大事なプロセスです。

開発者全員がPAT(classic)の何がいけないのか、なぜわざわざ移行しなきゃいけないのかを理解しているわけではありません。

そのためPAT(classic)の無効化を行う理由、背景を丁寧に明記したポリシードキュメントを作成し全社に展開。無効化の実行までは期間を取り、移行に伴うサポートが必要なメンバーには個別に対応を行なっていきました。

10Xではアナウンスから無効化までは3週間程度の期間を取りました。

notionで丁寧に色々と

無効化して問題は発生したか

PAT(classic)はその性質上、GitHubのOwner権限を持っていても利用状況を把握できません。そのため無効化時点で全ての洗い出しが完了したかどうかを確かめる術はなく、ある程度は決めで無効化するしかありません。

10Xでは前述してきたプロセスを経て無効化した結果、2件ほど業務が止まるケースが発生しました。しかしながら開発業務の一部に影響は出たが個別対応することですぐに解決できるケースのみで大きな業務影響は発生しませんでした。

PATはその性質上、プロダクト等で利用されることがないためある程度えいやで止めてしまっても業務にCriticalな影響は発生しづらい部類なのかなと思っています。一方で入念な準備なしで進めてしまうと大きな混乱を生む可能性も孕むため、丁寧に1つずつ愚直にやっていくのが大事かなと思います。

最後に

今回はGitHubのPATの取り扱いに関してお話しました。今回のケースに限らず、Credentialの利用がセキュアで無くなることはかなりよくある問題かなと思います。

また今回の影響範囲は主に開発環境に留まりましたが、プロダクトで利用するようなCredentialで似たような取り組みを行おうとするとかなり慎重に進める必要があります。

この件に限らず、セキュリティチームでは「何かをrevokeしたり削除したりする際にその安全性を誰も保証できない」状態がそもそも良くないよねという課題意識を持っており、これを改善するための施策も進めています。

プロセスに人間が関わる以上、どうしても自動化できない領域もあるのでバランスを取れた運用を引き続き目指していければと思います。