GitHubリポジトリにおけるレビュープロセスの統制

こんにちは。この記事は、Merpay Advent Calendar 2021 の13日目の記事です。メルペイのSREチームに所属している@yuharaです。 組織ではコンプライアンスの遵守が必要不可欠です。ソフトウェア開発のライフサイクルにセキュリティやコンプライアンスを組み込むことで、リスクを軽減するとともに、それらの管理を効率化できます。 メルペイではソースコード管理にGitHubを利用しています。この記事では、ソースコードのレビュープロセスに、組織として遵守すべきポリシーを適用し、それらを継続的に運用する方法を紹介します。

Branch Protectionを使う

Branch ProtectionはGitHubが提供する標準的な機能です。GitHub Freeではpublicリポジトリ、GitHub Pro、GitHub Team、GitHub Enterpriseではpublic及びprivateリポジトリで利用できます。 リポジトリ内でBranch Protectionを使うと、特定のブランチ、あるいは指定した名前のパターンにマッチするブランチに対してブランチ保護ルールを作成できます。設定できるルールとしては About protected branches で全ての項目が紹介されています。ここではいくつかを取り上げて紹介します。

Require pull request reviews before merging

この設定では、誰かがPull Requestを保護されたブランチにマージする前に、承認を必須とすることができます。これを有効にすると、最低でも1人あるいは指定した人数の承認がなければそのPull Requestを保護されたブランチにマージすることができません。コード所有者等が指定したレビュアーから必要な承認数が得られていない場合は、以下のようにマージをブロックします。 review_required

また Code Owners と組み合わせ、リポジトリ内のコードを所有する個人あるいはチームを定義することで、特定の担当者からの承認を必須とすることができます。 以下の CODEOWNERS のサンプルでは、このリポジトリのコードオーナーが team-a であるため、team-aに所属するメンバーからの承認が必須となります。

CODEOWNERS

* @org-name/team-a

Require status checks before merging

この設定では、保護されたブランチに変更を加える前に、すべての指定された(下記スクリーンショットのRequiredラベルが貼られた)CIをPassすることを必須の条件にすることができます。 これを有効にすると、Requiredラベルが貼られたCIが失敗した場合は保護されたブランチにマージできない状態になります。

status_check_required

Include administrators

デフォルトではブランチ保護ルールは、リポジトリへの管理者権限を持つユーザーには適用されません。そのため、管理者は例えばRequiredなCIが失敗していたとしても強制的に保護されたブランチにマージすることができます。Include administratorsを有効にすることで、リポジトリ管理者もブランチ保護ルールに含めることができます。

Restrict who can push to matching branches

この設定は GitHub Team または GitHub Enterprise Cloud を使用している場合に有効にできます。この設定によって、保護されたブランチにpushできる担当者やチームあるいは後述するアプリに限定することができます。

GitHub Appsで独自のポリシーを実装する

次にGitHub Appsを利用する方法です。先程はBranch Protectionについて紹介しましたが、要件によっては、Branch Protectionだけでは組織のポリシーを満たせないこともあります。 例えば、「Pull Requestをマージするために複数のチームからの承認を必須の条件としたい」といった場合です。

CODEOWNERSファイルで複数のチームをコード所有者とし、チームの数と同じ数の承認を要求するようにブランチ保護ルールを作成したとしても、ここで例に挙げた複数のチームからの承認を必須とするポリシーを実現することはできません。 以下のようにCODEOWNERSファイルを準備し、このリポジトリの所有者がteam-a と team-b の2つのチームであると定義します。ブランチ保護ルールで「2人のメンバーの承認が必要」とルールを作成した場合でも、例えばteam-aに所属するメンバー2人が承認すればブランチ保護ルールの条件を満たすため、 team-bの承認なしに保護されたブランチにマージできてしまいます。

CODEOWNERS

* @org-name/team-a @org-name/team-b

このような場合は、GitHub Appsを作成することで実現できます。GitHub Appsの構築方法はこちらで紹介されているのでここでは省略します。

先程 例に挙げた複数チームからの承認を必須の条件としたいという要件を実現するために、簡単なGitHub Appsを作成してみました。 このサンプルAppでは、Pull Request に /merge とコメントを残すと、webhookを介してGitHub Appsがイベントを受信し、そのPull Requestが要件を満たしているかどうかを確認し問題がなければマージします。team-aとteam-bによって承認されていない場合はマージすることはできません。

need_to_approve_from_all_team

team-aとteam-bによって承認されている場合、Appは要件を満たしていることを確認し Pull Requestをマージします。

merged

このようにGitHub Appsにマージを任せる場合、前述した Restrict who can push to matching branches で保護されたブランチへのpushを指定のアプリだけに限定することで、必ずGitHub Appsの使用を強制でき、独自のワークフローをリポジトリに構築することができます。

サードパーティー製のツールを使う

サードパーティー製ツールを利用する方法です。ここではOpenSSFが提供する2つのツールを紹介します。

Scorecard

Scorecardはセキュリティに関連するいくつかの重要な項目をチェックし、各チェックに0~10のスコアを割り当てるツールです。これらのスコアを利用して改善すべき点を特定し分析することができます。 利用方法は簡単で、いくつかの方法がドキュメントで説明されています。ここではOpenSSFが提供しているDockerイメージを使ってScorecardを実行しリポジトリのチェックを行います。 --checks オプションを指定することで特定の項目のみに絞る、あるいはカンマ区切りで複数のチェックしたい項目に対してチェックを行うことが可能です。 まず、Branch Protectionのステータスチェックを無効にした状態でBranch-Protection をチェックしてみます。

$ docker run -e GITHUB_AUTH_TOKEN=${GITHUB_TOKEN} gcr.io/openssf/scorecard:stable --checks=Branch-Protection --repo=https://github.com/${ORG_NAME}/${REPO_NAME}

RESULTS
-------
Finished [Branch-Protection]
Aggregate score: 7.0 / 10

Check scores:
|--------|-------------------|--------------------------------|------------------------------------------------------------------------------------------------------------------|
| SCORE  |       NAME        |             REASON             |                                            DOCUMENTATION/REMEDIATION                                             |
|--------|-------------------|--------------------------------|------------------------------------------------------------------------------------------------------------------|
| 7 / 10 | Branch-Protection | branch protection is not       | https://github.com/ossf/scorecard/blob/3233e4f5be15de851188b4be354bc4a44e1e3466/docs/checks.md#branch-protection |
|        |                   | maximal on development and all |                                                                                                                  |
|        |                   | release branches               |                                                                                                                  |
|--------|-------------------|--------------------------------|------------------------------------------------------------------------------------------------------------------|

スコアは満点の10点中7.0となっており、Branch Protectionが最大限に行われていないメッセージが出力されています。設定を修正した後で再びScorecardを実行します。

$ docker run -e GITHUB_AUTH_TOKEN=${GITHUB_TOKEN} gcr.io/openssf/scorecard:stable --checks=Branch-Protection --repo=https://github.com/${ORG_NAME}/${REPO_NAME}

RESULTS
-------
Aggregate score: 10.0 / 10

Check scores:
|---------|-------------------|--------------------------------|------------------------------------------------------------------------------------------------------------------|
|  SCORE  |       NAME        |             REASON             |                                            DOCUMENTATION/REMEDIATION                                             |
|---------|-------------------|--------------------------------|------------------------------------------------------------------------------------------------------------------|
| 10 / 10 | Branch-Protection | branch protection is fully     | https://github.com/ossf/scorecard/blob/3233e4f5be15de851188b4be354bc4a44e1e3466/docs/checks.md#branch-protection |
|         |                   | enabled on development and all |                                                                                                                  |
|         |                   | release branches               |                                                                                                                  |
|---------|-------------------|--------------------------------|------------------------------------------------------------------------------------------------------------------|

スコアが満点になりました。このようにリポジトリのチェックを自動化のツールを使って評価し、設定漏れなどによって発生し得るセキュリティリスクの軽減を図ることが可能となります。

Allstar

Allstarは先程紹介した Scorecard をより拡張させて GitHub Appsとして提供されています。Allstarを利用するとセキュリティのベストプラクティスに従わないリポジトリの設定などを継続的に監視し検出できるようになります。Allstarは、リポジトリがセキュリティのベストプラクティスに準拠していないことを検出すると、issueを作成したりセキュリティの設定を復元するなどのアクションを実行します。

インストールはAllstar の GitHub AppsをOrganizationもしくはユーザーリポジトリにインストールし、いくつかのYAMLファイルを準備するだけで利用可能です。Organizationで利用したい場合は .allstar という名前でリポジトリを作成し allstar.yaml を記述します。デフォルトでは opt-in 戦略が取られるため、Allstarを有効にするリポジトリを明示的に allstar.yaml に記述していく形になりますが、予め opt-out 戦略を指定しておくとデフォルトでOrganization配下のリポジトリ全てに有効にしておくこともでき、除外したいリポジトリをallstar.yaml に記述していく戦略も取れます。YAMLファイルの記述方法などについてはこちらを参照してください。

ここではブランチ保護ポリシーをAllstarで監視する一例を紹介します。リポジトリがポリシーに準拠していない場合、Allstarは指定されたアクションを実行します。2021年12月現在サポートされているアクションは log か issue の2つです。アクションに issue を指定した場合、Allstar App が対象のリポジトリに対して下記のような issue を作成します。

allstar

issue がすでに作成されている場合は24時間毎にコメントが追加されていき、違反が解決されると issue がクローズされます。Scorecard のように能動的にチェックするのではなく、継続的にリポジトリの設定を監視できます。 まだサポートされていませんが、ポリシー違反を検知した場合、自動的に適切な設定に変更する fix アクションも今後導入される予定のため、これによってポリシーを強制することが可能になるかもしれません。

まとめ

この記事では、GitHubリポジトリでレビューのプロセスを統制するいくつかの方法を紹介しました。今回はレビュープロセスを中心に紹介しましたが、コードへの予期せぬ脆弱性や認証情報などの混入を防止したり、定期的な監査によるログや権限などを確認するなど、GitHub上のソースコードを適切に管理するためには多層な仕組みを取り入れていく必要があると思います。今後機会があればそれらについても書いてみたいと思います。 最後まで読んでいただきありがとうございました。

明日のMerpay Advent Calendar 2021は、Backend Engineerの @poan さんです。お楽しみに!