🔑

GitHub Teams を用いてリポジトリアクセス権限を管理してみた

2024/06/24に公開

はじめに

キッズスターのプロダクト開発に於いて、外部の協力会社やパートナー [1] の方に携わっていただくことも少なくありません。
キッズスターがコードなどの管理に利用している GitHub では、組織外の方に携わっていただく場合、幾つかの選択肢が用意されています。
それぞれの方法毎に以下のようなメリット・デメリットが考えられます。[2]

  1. 個別のリポジトリに担当者を全員 Outside Collaborator として招待する
    • Pros: ちゃんと運用すれば確実に権限管理できる
    • Cons: 招待すべきリポジトリが多いとかなり大変
  2. Organizations の Member として担当者を全員招待する
    • Pros: 手っ取り早い
    • Cons: デフォルトだと全ての Private リポジトリが読めちゃう
  3. GitHub Enterprise を契約して Organization 管理を利用する
    • Pros: 高度な管理ができる
    • Cons: いかんせんお財布へのダメージがデカい

元々キッズスターでは 1. の方法で組織外の方に参画いただいていたのですが、規模が大きくなるにつれてシンドさが勝るようになってきました。

そこで、「なんとかできないか?」と調査したところ、「適切な権限管理を施しつつ協力会社・パートナーの方に参画してもらいたい!」「だけど、GitHub Enterprise は高くて手を出せない!」といった声に応えられる GitHub Teams 機能を用いた権限管理 という手法を発見し、実践したところ安定的に運用できているので、本稿ではその詳細について紹介します。

GitHub Teams is 何?

GitHub Teams は GitHub Organizations の機能の一つで、Team 毎に参照・編集可能なリポジトリを設定したり所属するメンバーを選択したりできる機能です。

Team は入れ子にすることができ、例えば

  • Proper: 社員全員が所属
    • Engineer: 社員のうちエンジニアが所属
      • Reviewer: エンジニアのうちレビュー担当者が所属
      • QA: エンジニアのうち QA 担当者が所属
    • Designer: 社員のうちデザイナーが所属
  • Cooperative: 協力会社全体を束ねる
    • Foo: 株式会社 Foo のメンバーが所属
    • Bar: 株式会社 Bar のメンバーが所属
      • QA: 株式会社 Bar のメンバーのうち QA 担当者のみ所属

といった階層構造を持たせることができます。

リポジトリの権限については、親から子に継承され、より強いもの(Read よりも Write が強い、など)で上書きされる仕組みとなっています。

また、親 Team に所属していないメンバーを子チームに所属させることなどもできます。[3]

何が嬉しいの?

GitHub Teams を用いた権限管理に於ける最大の利点は「協力会社やパートナーのメンバーを全員 Outside Collaborator ではなく Member 権限で Organization に招待」を実現できるところにあります。

これまでは上述の通り、一部の例外を除いて「開発に際して必要になる全てのリポジトリに Outside Collaborator として招待」という手法を採っていたため、開発担当者が増えると都度10個以上あるリポジトリを回って Outside Collaborator に追加していました。
この作業が思いの外大変で「1社につき3名まで」という制限を科す必要が出てくる事態になっていました。

GitHub Teams 機能を用いた権限管理にシフトして、事前に Team に対して適切なリポジトリ権限を設定しておくと、Member 権限で Organization に招待した上で Team に所属させるだけで良くなります。
楽ちん!

で、何をやるの?

GitHub Teams を用いた権限管理にシフトする場合、やることは大きく分けて以下の4つです。

  1. Base permissions の設定を現在の No permission に引き下げる
  2. GitHub Teams の階層構造構築
  3. Team 毎に適切なリポジトリ権限を付与
  4. (Optional) 新規リポジトリ追加時に社員用の Team に対して自動で Write 権限付与されるように仕込む

それぞれ詳細を掘り下げます。

1. Base permissions の引き下げ

Base permissions
Settings > Organizations > (対象の Orgs) > Member privileges

Base permissions とは Organization に対する設定の一つで、「Orgs 内の Member 権限を持つ人に対して付与される基本的なリポジトリ権限」のことです。

デフォルトでは「Read = リポジトリの読み込み権限あり」となっている [4] ため、このまま協力会社・パートナーの方を Member 権限に昇格させてしまうと、全てのリポジトリを閲覧できる状態になり、ガバナンス的によろしくない状態となってしまいます。

よって、一旦 Base permission を「No permission = public リポジトリと許可された private リポジトリ以外は閲覧・編集不可」として、許可されたリポジトリ以外は閲覧・編集できないようにする必要があります。

2. GitHub Teams の階層構造再編

Team Hierarchy
黒塗りだらけですがご容赦ください😓

ガバナンス的に問題ないかな?と言えるレベルまで情報統制をしつつ、運用的にも負担が少ない構造に Teams を再編します。

具体例として、キッズスターでは以下のような構造にしています。[5]

  1. Proper
    • 組織の全社員が所属
    • 全リポジトリへの Write 権限付与
    1. Reviewer
      • PR レビュワー
      • メンション用に分ける
    2. Partner
      • パートナーさんが所属
      • キッズスターではプロパーと同程度の権限を付与しているので Proper の子階層
    3. Quality Assurance
      • QA 関連のメンバーを束ねる論理階層
      1. Fuga Quality Assurance [6]
        • Fuga 社のメンバーのうち QA 関連担当のメンバーが所属
        • Fuga 社には全てのプロジェクトの QA を委託する可能性があるので Proper の子階層
  2. Cooperative Company
    • 協力会社を束ねる論理階層
    1. Hoge
      • Hoge 社のメンバーが所属
      • 担当する案件関連のリポジトリへの Write 権限付与
    2. Fuga
      • Fuga 社のメンバーが所属
      • 担当する案件関連のリポジトリへの Write 権限付与
    3. Piyo
      • Piyo 社のメンバーが所属
      • 担当する案件関連のリポジトリへの Write 権限付与

3. Team 毎の権限付与

上述の例で言うところの Proper Team は全ての private リポジトリに対する Write 権限をつける必要があります。
リポジトリ数が少なければ手動で頑張っても良いと思いますが、リポジトリ数が多い場合は API を叩いて設定しちゃうのが確実です。

詳細は省きますが、実際に筆者が書いて叩いた Bash スクリプトを以下に置いておきますので参考にしてみてください。

grant-all-repositories.bash

前提条件 / コマンド

以下のコマンドがインストールされており、パスが通っていることを期待します。

  • curl
  • jq
  • xargs

前提条件 / GITHUB_TOKEN

以下の権限を設定した Fine-granted Personal Access Token を環境変数 GITHUB_TOKEN に設定してください。[7]

Organization permissions

  • Member: Read 以上

Repository permissions

  • Administration: Read and Write
  • Metadata: Read 以上
  • Contents: Read 以上

スクリプト

#!/usr/bin/env bash

ORGANIZATION="kidsstar"
TEAM="${1}"

if [ -z "${TEAM}" ]; then
  echo "第一引数にチームIDを指定してください"
  exit 1
fi
if [ -z "${GITHUB_TOKEN}" ]; then
  echo "環境変数 GITHUB_TOKEN を設定してください"
  exit 1
fi

page=0
while : ; do
  page=`expr "${page}" + 1`
  # https://docs.github.com/en/rest/repos/repos?apiVersion=2022-11-28#list-organization-repositories
  response=$(
    curl -s \
      -H "Accept: application/vnd.github+json" \
      -H "Authorization: Bearer ${GITHUB_TOKEN}" \
      -H "X-GitHub-Api-Version: 2022-11-28" \
      https://api.github.com/orgs/${ORGANIZATION}/repos\?type=private\&sort=full_name\&direction=asc\&per_page=100\&page=${page}
  )
  count=$(echo "${response}" | jq '.|length')
  if [ "${count}" -le 0 ]; then
    break
  fi
  echo "Page: ${page}, Count: ${count}"
  repositories=($(echo "${response}" | jq -r '.[].full_name' | xargs))
  index=0
  while [ "${index}" -lt ${count} ]; do
    repository_name=${repositories[${index}]}
    if [ -z "${repository_name}" ]; then
      continue
    fi
    # https://docs.github.com/en/rest/teams/teams?apiVersion=2022-11-28#add-or-update-team-repository-permissions
    curl -s \
      -X PUT \
      -H "Accept: application/vnd.github+json" \
      -H "Authorization: Bearer ${GITHUB_TOKEN}" \
      -H "X-GitHub-Api-Version: 2022-11-28" \
      https://api.github.com/orgs/${ORGANIZATION}/teams/${TEAM}/repos/${repository_name} \
      -d '{"permission": "push"}'
    echo "Grant '${TEAM}' Write permission to '${repository_name}'"
    index=`expr "${index}" + 1`
    sleep 1
  done
  sleep 1
done

協力会社用 Team のリポジトリ権限設定に関しては、全ての協力会社に共通して Read 権限などを付与すべきリポジトリがある場合は Cooperative Company Team に付与したり、案件毎にグルーピング用の Team を作ることで階層化して管理することができるので、状況に応じて設定してみてください。
なお、後付けでグルーピング用の Team を作った場合でも、設定から属する親 Team を変更可能です。
親 Team の変更

4. (Optional) 新規リポジトリ作成時に社員用 Team に対して Write 権限自動付与

Base permissions を No permissions に設定したことにより、Organizations に新規リポジトリが作成された際に、当該リポジトリへのアクセス権が未設定な状態となってしまいます。
そのため、リポジトリ作成時に自動的に社員用 Team (上述の例だと Proper Team)に権限を自動設定すると良さそうです。

キッズスターでは、 GitHub App の Webhook として Repository の Action = created に反応するようなモノを組みました。
機会があれば、そちらの実装などについても記事を書ければと思っています。

運用はどう変わるの?

ざっくりいうと以下の6点が変更点となります。

  1. 協力会社さんを新しく開拓した際に Team を作成する
    • Cooperative Company 以下に Team を追加します。
    • 担当するプロジェクトに応じて参照が必要になるリポジトリを Read 権限で設定します。
    • 担当するプロジェクト固有のリポジトリを Write 権限で設定します。
  2. Team 作成済みの協力会社さんへの委託が含まれるプロジェクトがスタートする際にリポジトリへのアクセス権を追加設定する
  3. 会社にメンバーがジョインした際に社員用 Team に追加する
  4. 新しく協力会社さんのプロジェクトメンバーを追加する際には、Outside Collaborator ではなく Member として招待した上で、該当する会社の Team に Member として追加する
    • Member 追加時に所属先の Team を選択できますが、追加を忘れてしまった場合は Teams の設定からメンバー追加可能です。
  5. プロジェクト終了時にプロジェクトを担当していた協力会社のメンバー(のうち継続して別案件に入っていないメンバー)は全員 Organizations から削除する
    • シート数節約の目的があります。

まとめ

本稿では GitHub Teams 機能を用いて、運用負荷を下げつつ、適切に情報統制する方法について解説しました。
キッズスターではこの運用を始めたばかりですが、今の所うまく機能しております 🎉

この手の「組織の規模やルールによってまちまち」になりがちな情報はあまりネット上に転がっておらず、実践済みの方にとっては「んなの基本じゃん…。」と思われるかもしれません。
しかしながら、実際にこの運用に辿り着くまで紆余曲折と試行錯誤があったので、「ウチはこんな規模で、こんな運用をしてるよ!」などの情報が増えると嬉しいなぁという思いも込めて、キッズスターの事例をご紹介いたしました。

脚注
  1. キッズスターでは業務を委託するフリーランスの方を「パートナーさん」と呼んでいます ↩︎

  2. 厳密に言うと、「代表者を Member なり Outside Collaborator なりで招待して、リポジトリをフォークしてもらって PR を送ってもらう」といった方法もあるっちゃあります。 ↩︎

  3. 例えば 「Cooperative > Bar には所属していないが Cooperative > Bar > QA には所属している」といったメンバーを設定可能。 ↩︎

  4. キッズスターではデフォルトを Write に設定していたので、スクショでは Write に設定されています。 ↩︎

  5. もちろん、会社名とかはボヤかして書いてます。 ↩︎

  6. 階層を問わず、同名の Team は作成できないため、会社名を Prefix にしています。 ↩︎

  7. 色々試しながら設定したので、一部要らないものがあるかもしれません 🙇‍♂️ ↩︎

KidsStar Inc.

Discussion