FLINTERS Engineer's Blog

FLINTERSのエンジニアによる技術ブログ

DependabotとGitHub Actionsを使い、低コストでパッケージやライブラリをアップデートする

こんにちは、株式会社FLINTERSでエンジニアをやっている丸山です。

この記事は2024年1月にFLINTERSが10周年を迎えることを記念して、133日連続でブログを書き続けるチャレンジの一環として書かれました。本記事は129日目の記事となります。

以前73日目の記事も執筆しましたので、よければそれもご覧になってください。

今回の記事は、ソフトウェア開発で重要なセキュリティについて、ソフトウェアが利用しているパッケージやライブラリをソフトウェアのコードを管理しているGitHub上でDependabotやGitHub Actionsを利用することによって低コストで定期的にバージョンの更新を行い、セキュリティリスク下げる取り組みをしたので、それについて書きたいと思います。

はじめに

 ビッグデータという言葉がもてはやされ、研究者などの専門家以外でもOpenAIのChatGPTなどを通してAI技術に簡単に触れられるようになった現在、情報はますます重要なものとなっています。 またこれらの情報をソーシャル・ネットワーキング・サービス(SNS)などを使いインターネットを通して多くの人に配信をするような通信技術も同様に重要です。 この2つはまとめて情報通信技術(Information and Communication Technology: ICT)と呼ばれますが、このICTが発展・普及した経済と社会をデジタル経済と呼ぶ1ほど今日ではICTは重要視されています。

 このICTを支える技術の1つにソフトウェアがあり、パソコンやスマートフォンのOSからブラウザ、ブラウザで閲覧するWebページを配信するWebアプリケーションなど、さまざまなところにソフトウェアは存在しています。デジタル経済と呼ばれる現在ではこのソフトウェアの信頼性は社会基盤の安定性を左右するほどとなり、ほとんどのビジネスにおいてもソフトウェアは重要であると言えるでしょう。

 そんな社会基盤を担い、ビジネスにおいても重要なソフトウェアですが、近年はそのソフトウェアを取り扱うものに対して被害を与えるサイバー攻撃による被害が拡大しています2。 サイバー攻撃による被害を受けると、金銭の詐取等の直接的な被害だけでなく、株価や純利益が下落・減少するといった間接的な被害が発生します3。他にも業務停止といった被害の事例もあり、その被害は無視できないものとなっています。 このような被害を防ぐソフトウェア開発者ができる対策の1つにソフトウェアが利用しているライブラリ・パッケージを更新するという方法があります。

 今日の複雑化し、かつ高速に開発することを求められるソフトウェア開発では、世界中の開発者によって開発され公開されているOSS(Open Source Software)やFOSS(Free and Open Source Software)と呼ばれるソフトウェアをライブラリーやパッケージという形で利用するということは珍しくなく、2022年時点で開発されている商用ソフトウェアを対象にした調査では、ソフトウェアの96%にOSSが含まれている4という調査結果が報告されるほど一般的となっています。 このようにOSSのライブラリやパッケージの利用による恩恵は大きく、それを利用することも一般的となっている現在ですが、それを起因とするセキュリティの懸念というのも存在します。 この懸念には大きく2つのパターンが存在しており、OSSの開発者が悪意を持って利用者に被害を与えるプログラムを仕込むマルウェアと、開発者が意図せずにコードの欠陥を仕込ませてしまう脆弱性が存在します5

 コードの欠陥によって生じた脆弱性で近年最も有名な脆弱性は2021年にJavaベースのロギング用ライブラリであるApache Log4jで発見された任意コード実行が行える通称Log4Shellと呼ばれる脆弱性(CVE-2021-442286)です。 この脆弱性は攻撃者が送信した文字列をLog4jがログとして記録すると、その文字列に記述された通信先やサーバー内部のファイルを読み込んで実行するもので、インターネット上に公開されたサーバーからマルウェアをダウンロードさせWebサーバーにマルウェアを感染させることが可能となる内容であり7、実際にLog4jを利用したソフトウェアを動かしているサーバーがランサムウェアのマルウェアに感染させられたり、暗号資産のマイニングを行うマイナーを設置されたりする被害が報告されています8。 この脆弱性に対する対策はソフトウェアが利用しているLog4jのバージョンを脆弱性が修正されたバージョンに更新するという方法です。

 このようにメンテナンスが行われているOSSでは脆弱性が発見されるとその脆弱性を修正したバージョンが公開されるのが一般的であり、OSSの利用者はその修正したバージョンにライブラリやパッケージを更新することによって脆弱性を防ぐことができます。 しかし実際にはライブラリやパッケージに脆弱性が発見され、それを修正するバージョンが公開されても、修正したバージョンを利用せずに脆弱性が存在するバージョンを利用し続けられていることが少なくないことが確認されています。 公開されているJavaのライブラリの配布を行っているMaven Centralを対象にした調査では、ダウンロードされている脆弱性が確認されているライブラリの96%はアップデートすることによってその脆弱性を防げることがわかっています[^5]。また、最悪の脆弱性とまで言われたLog4Shellからほぼ2年が経過した現在でもLog4jのダウンロードの約4分の1がLog4Shell対処前の脆弱性があるバージョンであることもわかっています[^5]。

 脆弱性が含まれるライブラリやパッケージのほとんどがアップデートを行うことによってその脆弱性が防ぐことが可能であるにもかかわらず、アップデートされない理由はなぜなのでしょうか? これは多くの開発者にとってセキュリティが最優先事項でないことが原因の1つにあります。 開発者はセキュリティに関心がないというわけではありませんが、セキュリティを優先することによって納期が守れないことを回避したり、セキュリティについて学習する時間が不十分などの理由により、セキュリティについて積極的に取り組めておらず9、ライブラリやパッケージを最新に保つことを苦痛と考えるような開発者も52%いると調査されています10

 このような課題を解決する方法の1つにDependabot11やRenovate12など、ソフトウェア開発を行っているリポジトリで利用しているライブラリやパッケージなどの依存関係の更新を行う支援をしてくる仕組みを導入する方法があります。 DependabotやRenovateはGitHub13やGitLab14などのソフトウェアのコードを管理するサービスに導入が可能であり、導入すると管理を行っているソフトウェアのコードが利用している依存関係を管理しているPackage managerを利用して、利用している依存関係に最新バージョンがリリースされていないか定期的にチェックをしてくれるようになります。 もしチェックした時に利用している依存関係に最新バージョンがリリースされていれば、Dependabotなどはそのソフトウェアが最新のバージョンを利用するように変更を行うPull RequestやMerge Requestを作成してくれます。

そこで我々の開発チームでは、開発物を管理しているGitHub上のリポジトリに対してDependabotを導入し、依存関係の更新にかかるコストを抑えつつ更新することによってセキュリティリスクを削減する仕組みを導入しています。 この記事では、チームで導入しているDependabot運用ルールを説明しつつ、その設定方法について書きたいと思います。

Dependabotについて

 Dependabotは2017年にスタートしたプロジェクト15で、2019年にGitHubに買収され16、2021年にGitHubから正式にサービス提供され始めました17。 現在Dependabotは、使用している依存関係に存在する脆弱性を通知するDependabot alerts18、既知の脆弱性を更新するDependabot security updates19、依存関係を最新に保つDependabot version updates20が正式に提供されており、アラートを管理するDependabot auto-triage rules21がベータ版22で提供されています。

 ベータ版であるDependabot auto-triage rules以外について、それぞれについて説明すると次のようになります。

Dependabot alerts

 リポジトリ内のコードが使用している依存関係をスキャンし、安全でない依存関係を検出したときに通知を行います。 スキャンを行うタイミングはリポジトリの依存関係が更新された時と、既知のセキュリティの脆弱性とマルウェアを記録しているGitHub Advisory Databaseに新しい脆弱性またはマルウェアが登録されたときです23.24

もし、脆弱性やマルウェアが検出されたときは、リポジトリのSecurityタブにアラートが表示され、リポジトリのメンテナーに対してはメールで通知を行います。 このほかにも、mainなど特定のブランチに対して行われたPull Requestで変更される依存関係を確認し、既知の脆弱性があるか確認することもできます。

Dependabot security updates

 Dependabot alertsが安全でない依存関係を検出した時に、依存関係の更新が最小限で済むような脆弱性を解決するPull Requestを作成します。 これによって作成されたPull RequestをマージをするとDependabot alertsの警告が解決済みとして扱われます。

Dependabot version updates

 リポジトリ内のコードが使用している依存関係のバージョンを最新に保つためのPull Requestsを作成します。 リポジトリに.github/dependabot.ymlを設置し、更新を行いたいパッケージシステムや更新頻度などの設定を記述する必要があります。

開発チームについて

 現在所属しているチームの開発者は7~8名で、開発言語にはTypeScript, Rust, Python, Terraform, Scalaなどが使用されています。 チームでは複数のリポジトリで開発を行っていますが、全員が全てのリポジトリ開発に関わっているわけではなく、得意分野や関心、プロジェクトのドメイン領域などによって複数の担当リポジトリを受け持っています。 Pull RequestsをMergeする際はレビューを行うルールもあり、GitHub Actionsを利用したCIによるチェックも行っています。 リポジトリの設定ではCIによるチェックとレビューを強制するためにBranch protection ruelesを設定しており、リポジトリの担当者を明示的に示す.github/CODEOWNERSに記載された担当者からのレビューが必要です。

チームのDependabotのルール

 チームではどのリポジトリに対しても最低月1回は依存関係のアップデートを行うルールがあります。 しかしDependabotが同時に開けるPull Requestsの数には上限があり最大で10個までであるため25、1月で10個以上Pull Requestsが作成されると次のチェックを行うタイミングまで無視された依存関係の更新ができないという理由から、必要に応じて週1回や毎日にDependabotが更新を確認するタイミングを増やします。

 また、Dependabotが作成したPull Requestsのレビューを行う担当者は.github/CODEOWNERSに記載されている人が行います。 ただし、npmを扱うリポジトリなどは依存関係の数も多く、更新頻度も高いため、全てのPull Requestsに対して全員がレビューを行うのは開発者の負荷が高くなるという理由から、 依存関係がアプリケーションに直接利用されるものか、開発時にのみ使われるものか、加えて更新の内容のバージョンがMajor Update, Minor Update, Patch Updateなのかに応じて、自動でマージを行う、もしくはレビューを行う人数を減らすなどの工夫を行なっています。 これを整理すると次のようなルールでレビューとマージを行なっています。

依存の種類 更新のバージョン 自動マージ 人間のレビュー
アプリが使う Major しない 全員
アプリが使う Minor しない 抽選で選ばれた2人
アプリが使う Patch する しない
開発用 Major しない 全員
開発用 Minor する しない
開発用 Patch する しない

DependabotとGitHub Actionsの設定

 前章ではチームでのDependabotの運用のルールを示しました。本章ではこれを実際に動かすためにGitHubの設定とDependabotやGitHub Actionsのymlの設定を解説していきます。

CODEOWNERS

 まず、リポジトリのレビューの担当者を示すCODEOWNERSについて設定していきます。 CODEOWNERS26.github/CODEOWNERSに配置し、行ごとにファイルやディレクトリとその担当者を記述することによってその担当箇所の変更が含まれるPull Requestのレビューを強制することができます。

例えば次のように設定を行うとリポジトリ内の全てのファイルに対してuser1, user2, user3のレビューを強制することができます。

* @user1 @user2 @user3

 しかし、これに記述されるとdependabotが作成したPull RequestsでもCODEOWNERSに記載されたレビュワーにレビュー依頼が飛んでしまうため、もしPull Requestsの内容に応じてレビュー担当者を変えたい場合は、Dependabotが変更を行うファイルの担当者がいないことを記載してあげる必要があります。

* @user1 @user2 @user3

# Dependabotが変更を行うファイルを記載し、後ろにユーザーIDを記載しないことによってDependabotが作成したPull Requestsに強制的にレビュワーが割り当てられない
/package.json
/package-lock.json

dependabot.yml

 実際にDependabotで依存関係の更新を行うPull Requestsを作成する設定を書きます。 Dependabotの設定は.github/dependabot.ymlに記述します。 例えば、リポジトリのルートに置かれている./package.json./package-lock.jsonを使用して利用しているnpmパッケージの更新がないかを月1回確認する設定は次のようになります。

version: 2
updates:
  # npmのバージョン更新を有効にする
  - package-ecosystem: "npm"
    # rootディレクトリに置かれているpackage.jsonとlockファイルを使用する
    directory: "/"
    # 毎月1日にnpmレジストリを更新のチェックをする
    schedule:
      interval: "monthly"

 これが基本的な設定ですが、dependabotが作成するPull Requestsにレビュワーを割り当てたり、チェックを行う時間の指定を行うようにすると次のようになります。

version: 2
updates:
  # npmのバージョン更新を有効にする
  - package-ecosystem: "npm"
    # rootディレクトリに置かれているpackage.jsonとlockファイルを使用する
    directory: "/"
    # 毎月1日に日本時間の10時にnpmレジストリを更新のチェックをする
    schedule:
      interval: "monthly"
      timezone: "Asia/Tokyo"
      time: "10:00"
    # 作成されたPull Requestsにuser1, user2, user3のレビュワーを割り当てる。
    reviewers:
      - "user1"
      - "user2"
      - "user3"

 ただしチームでは、レビュワーは更新の内容に応じて動的に割り当てるようにしてあるため、reviewersは設定せず、また、デフォルトでは同時に作成されるPull Requestsの数が5つまでで、それ以上は次のチェックのタイミングまで作成されないため数を緩和する設定も行い次のようになっています

version: 2
updates:
  # npmのバージョン更新を有効にする
  - package-ecosystem: "npm"
    # rootディレクトリに置かれているpackage.jsonとlockファイルを使用する
    directory: "/"
    # 毎日に日本時間の10時にnpmレジストリを更新のチェックをする
    schedule:
      interval: "daily"
      timezone: "Asia/Tokyo"
      time: "10:00"
    # デフォルトで作成できるPull Requestsの数は5個なので10個まで作成できるようにする
    open-pull-requests-limit: 10
  # GitHub Actionsが利用しているActionsのバージョンは月1回確認を行う
  - package-ecosystem: "github-actions"
    # github-actionsかつdirectoryが/の場合.github/workflowsが参照される
    directory: "/"
    schedule:
      interval: "monthly"
      timezone: "Asia/Tokyo"
      time: "10:00"
    open-pull-requests-limit: 10

 また、更新がある依存関係に関して通常はそれぞれPull Requestsが作成されますが、groupesを使うことによって、複数のライブラリの更新をまとめて1つのPull Requestsで更新を行うことができます。

version: 2
updates:
  # npmのバージョン更新を有効にする
  - package-ecosystem: "npm"
    # rootディレクトリに置かれているpackage.jsonとlockファイルを使用する
    directory: "/"
    # 毎日に日本時間の10時にnpmレジストリを更新のチェックをする
    schedule:
      interval: "daily"
      timezone: "Asia/Tokyo"
      time: "10:00"
    # デフォルトで作成できるPull Requestsの数は5個なので10個まで作成できるようにする
    open-pull-requests-limit: 10
    # reactに関する依存関係の更新を1つのPull Requestsで行う
    groups:
      react:
        patterns:
          - "react"
          - "react-dom"
          - "@types/react"
          - "@types/react-dom"

GitHub Actionsを使い自動マージや動的にレビュワーを割り当てられるようにする

 dependabot.ymlを設定することによってDependabotが依存関係を更新するPull Requestsを作成するようになりましたが、作成されるPull Requestsの量が多いと開発者はそのレビューが負担となり普段の開発の速度が遅くなる、もしくはDependabotが作成したPull Requestsを無視するようになります。 そこでレビューの負担を下げるために自動マージや影響度が低そうな更新についてはレビューを行う人数を減らすなどの対応が取れます。 これを実行するにはGitHubが提供しているCI/CD環境であるGitHub Actionsを使用する方法があります。

 ここではGitHub Actionsを利用してDependabotが作成するPull Requestsに自動マージと動的にレビュワーを割り当てる機能を実装します。

リポジトリに対する設定

 まずは、自動マージが行えるようにリポジトリの設定を行います。 GitHubで設定を行いたいリポジトリのSettingsを開きます(https://github.com/[user-name]/[repository-name]/settings)。GeneralからPull Requestsの項目にあるAllow auto-mergeにチェックを入れます。

GitHub リポジトリのSettingsから自動マージを許可できるようになるAllow auto-mergteの設定項目

 これを入れることによって、各Pull Requestsで自動マージを行う許可を出せ、許可を出すとBranch protections rulesで定めたルールを満たした時に自動でマージを行うようになります。 これは各Pull Requestsごとに自動マージを行う許可を出すため、通常の開発ではPull Requestsのページから自動マージの許可を出さなければ影響はありません。

 前章では自動マージを行う条件としてPatch Updateか、開発のみに使用されるパッケージのMinor Updateかという条件が示されていましたが、そのほかにもCIが問題なく通った場合にマージをするという条件もあります。このため、CIが通らなかった場合はマージされないようにする必要があるため、Branch protection ruleでしっかり条件を示してあげる必要があります。

 このBranch protection ruleは通常の開発で人間が作成したPull RequestsとDependabotが作成したPull Requestsでルールを分けることができないため、両方を満たすように条件を設定してあげることが必要です。

 例えば通常の開発でレビューを必須とした場合 Require a pull request before merging をチェックし、ブランチの内容が必ずPull Requestから更新されるようにし、Require approvalsをチェックすることによって最低n人からのapproveがないとマージできないようにするという設定が行えます。また、Require review from Code Ownersをチェックすることによって.github/CODEOWNERSに記載されたユーザーがapproveを行わないとマージができないようにすることもできます。

Branch protection ruleでレビューを必須とする設定

 加えて、CIによるチェックを必須とした場合は、Require status check to pass before mergingにチェックを入れることによって、CIが失敗した時にマージができないようにすることが可能です。CIによるチェックを必要とする場合は同時に Status checks that are requiredにチェックを行うCIの名前も記載しておくと、記載されたCIが正常に終了するまでマージができないようになります。 もし、自動マージを有効にする場合は、Status checks that are requiredにCIの名前を記載しておかないと、CIが動く前に正常と判断されてマージされてしまう場合があります。

Branch protection ruleでCIによるチェックを必須とする設定

 また、自動マージを行う際は自動マージされたPull Requestsか区別がつきやすいようにlabelをつけるようにしておくことがおすすめです。 IssueやPull RequestsからLabelsを選択することによってhttps://github.com/[user-name]/[repository-name]/labels に移動するので、そこからNew labelをクリックして自動マージかどうか識別しやすいラベルをあらかじめ作っておきます。 Dependabotが作成したPull Requestsにはdependenciesと対応する言語のラベルがつくので、Dependabotが作成したPull Requestsの識別は簡単です。

Dependabotによって作成されたPull Requestsを識別しやすくするために自動マージについてのauto mergeラベルを作成する

以上でリポジトリに対する設定は完了です。以降は自動マージと動的にレビュワーを割り当てるGitHub Actionsを設定します。

GitHub Actionsの設定

 GitHub ActionsはGitHubが提供しているCI/CD環境です。これを利用することによってDependabotが作成したPull Requestsにのみ自動マージを行うなどのタスクを実行させることができます。

まず、GitHubが自動マージについて簡単な例を示している27のでそれを解説します。

name: Dependabot auto-merge
on: pull_request

permissions:
  contents: write
  pull-requests: write

jobs:
  dependabot:
    runs-on: ubuntu-latest
    # Pull Requestを開いたユーザーがdependabotの場合のみ実行する
    if: github.actor == 'dependabot[bot]'
    steps:
      - name: Dependabot metadata
        id: metadata
        uses: dependabot/fetch-metadata@v1
        with:
          github-token: "${{ secrets.GITHUB_TOKEN }}"
      - name: Enable auto-merge for Dependabot PRs
        if: contains(steps.metadata.outputs.dependency-names, 'my-dependency') && steps.metadata.outputs.update-type == 'version-update:semver-patch'
        run: gh pr merge --auto --merge "$PR_URL"
        env:
          PR_URL: ${{github.event.pull_request.html_url}}
          GH_TOKEN: ${{secrets.GITHUB_TOKEN}}

 このworkflowはPull Requestsが開かれた時、同期された時、再度開かれた時に実行されます28。 実行されるとこのPull Requestsが開いたユーザーを確認し、それがDependabotだった場合は処理を続行します。 stepsには実行したい内容を記述しますが、1つ目のstepではdependabot/fetch-metadata29を使用してDependabotが作成した更新の内容を取得しています。2つ目のstepでは取得した更新の内容を確認し、更新する依存関係の名前がmy-dependencyかつ、アップデートのバージョンがPatchバージョンの時に自動マージを行う許可を出しています。 この条件を満たして自動マージを行う許可が出た場合は、Branch protection ruleを満たした時に自動でマージされるという流れになります。 また、permissionsはdependabotが作成した更新内容の取得や、自動マージを許可するのに必要なものとなっています。

 では、これについてマージする条件をPatchバージョンの更新もしくは開発依存かつMinorバージョンの更新であるというものに更新し、Pull Requestsに対してApproveを出し、自動マージに関するPull Requestであることを表すauto mergeのラベルをつけるようにします。

name: Dependabot auto-merge
on:  pull_request

permissions:
  contents: write
  pull-requests: write
  issues: write
  repository-projects: write

jobs:
  dependabot:
    runs-on: ubuntu-latest
    # Pull Requestを開いたユーザーがdependabotの場合のみ実行する
    if: github.actor == 'dependabot[bot]'
    steps:
      # 依存関係の更新内容の取得
      - name: Dependabot metadata
        id: metadata
        uses: dependabot/fetch-metadata@v1
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
      # 自動マージ対象の場合はPull Requestにauto mergeのラベルを貼り、自動マージの許可をする
      - name: Enable auto-merge for Dependabot Pull Requests
        # メジャーバージョンは自動マージしない
        # パッチバージョンもしくはdevDependenciesのとき自動マージする
        if: |
          steps.metadata.outputs.update-type != 'version-update:semver-major' && 
          (steps.metadata.outputs.update-type == 'version-update:semver-patch' ||
          steps.metadata.outputs.dependency-type == 'direct:development')
        run: |
          gh pr review --approve "$PR_URL"
          gh pr edit "$PR_URL" --add-label "auto merge"
          gh pr merge --auto --merge "$PR_URL"
        env:
          PR_URL: ${{github.event.pull_request.html_url}}
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

ラベルをつけるためにpermissionsが増えたりしましたが、ここまでは問題ないと思います。

 次は自動マージ対象じゃないPull Requestsに対して、レビュワーを割り当てるようにします。 レビュワーは、Majorバージョンの更新の時はリポジトリの担当者全員に、そうでない時は担当者からランダムに2名を抽出して割り当てることになります。

 この時、リポジトリの担当者は.github/CODEOWNERSに記載されているので、管理の箇所を複数に増やさないためにそれを利用します。

 レビュワーを割り当てる処理の内容としては次のようになります。
 まず、レビューを割り当てようとしているPull Requestsにすでにレビュワーが割り当たっていないか確認をします。これは、workflowsの発火条件がPull Requestsが作成されたとき以外のも、同期した時、再度開いた時にもあるため、重複で割り当てないようにするためです。 また、この時レビューに割り当てられたユーザーを確認するとともにレビュー済みのユーザーも確認します。これはレビューに割り当てられたユーザがレビューを行うと、レビューに割り当てられたユーザを取得するAPIから取得できなくなるためです。 すでにレビューしたユーザやレビューに割り当てられているユーザが存在しないことを確認すれば、次はリポジトリの担当者が記載されているCODEOWNERSから担当者を取得し担当者についてのArrayに変換します。 そしたら条件に応じてレビューを割り当てるユーザーを選択し、レビュワーを割り当てます。

name: Dependabot automation
on:  pull_request

permissions:
  contents: write
  pull-requests: write
  issues: write
  repository-projects: write

jobs:
  dependabot:
    runs-on: ubuntu-latest
    if: ${{ github.actor == 'dependabot[bot]' }}
    steps:
      - name: Dependabot metadata
        id: metadata
        uses: dependabot/fetch-metadata@v1
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
      - name: Enable auto-merge for Dependabot Pull Requests
        # メジャーバージョンは自動マージしない
        # パッチバージョンもしくはdevDependenciesのとき自動マージする
        if: |
          steps.metadata.outputs.update-type != 'version-update:semver-major' && 
          (steps.metadata.outputs.update-type == 'version-update:semver-patch' ||
          steps.metadata.outputs.dependency-type == 'direct:development')
        run: |
          gh pr review --approve "$PR_URL"
          gh pr edit "$PR_URL" --add-label "auto merge"
          gh pr merge --auto --merge "$PR_URL"
        env:
          PR_URL: ${{github.event.pull_request.html_url}}
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      # 自動マージ対象じゃないPull Requestsに対してレビュワーを割り当てる
      - name: Set Reviewers in Dependabot Pull Requests
        if: |
          steps.metadata.outputs.update-type == 'version-update:semver-major' || 
          (steps.metadata.outputs.update-type != 'version-update:semver-patch' &&
          steps.metadata.outputs.dependency-type != 'direct:development')
        uses: actions/github-script@v7
        env:
          UPDATE_TYPE: ${{ steps.metadata.outputs.update-type }}
        with:
          script: |
            // レビューに割り当てられ、未レビューのユーザーを取得
            const assignedUsersResponse = await github.rest.pulls.listRequestedReviewers({
              owner: context.repo.owner,
              repo: context.repo.repo,
              pull_number:  context.payload.pull_request.number,
            });
            const assignedUsers = assignedUsersResponse.data.users.map(user => user.login);

            // レビュー済みのユーザーを取得
            const reviewedUsersResponse = await github.rest.pulls.listReviews({
              owner: context.repo.owner,
              repo: context.repo.repo,
              pull_number:  context.payload.pull_request.number,
            });
            const reviewedUsers = reviewedUsersResponse.data.map(review => review.user.login);

            // すでに割り当て済み、もしくはレビュー済みの場合は終了
            if (assignedUsers.length !== 0 || reviewedUsers.length !== 0) return;
            
            // CODEOWNERSからユーザーを取得
            const codeownersContent = await github.rest.repos.getContent({
              owner: context.repo.owner,
              repo: context.repo.repo,
              path: '.github/CODEOWNERS',
            });
            const codeownersText = Buffer.from(codeownersContent.data.content, 'base64').toString();
            const users = codeownersText.split('\n')[0].split(' ').slice(1).map(user => user.slice(1));
            
            // レビューするユーザーをCODEOWNERSからMajor-updateの場合は全員、それ以外はランダムに2人割り当てる
            const updateType = process.env["UPDATE_TYPE"];
            const copy = users.slice();
            const reviewers = updateType === 'version-update:semver-major'
              ? users
              : [...Array(2)].map(() => copy.splice(Math.floor(Math.random() * copy.length), 1)[0]);

            // レビュワーを割り当てる
            await github.rest.pulls.requestReviewers({
              owner: context.repo.owner,
              repo: context.repo.repo,
              pull_number:  context.payload.pull_request.number,
              reviewers: reviewers,
            });

 以上で、Dependabotが作成したPull Requestsについて自動マージとレビュワーを動的に割り当てられるようになりました。

 余談ですが、レビュワーを割り当てる時はgithubのapiを利用することになると思いますが、ghコマンドを使ってgh apiで割り当てようとする場合はDependabotが作成したPull Requestsで動くGitHub Actionsが持てる権限では権限不足で割り当てることができません30。これを動かそうとする場合は個人のPATを利用する方法もありますが、ghコマンドが使うようなGraphQL APIではなくREST APIを使用すると権限不足で怒られることがないので、もしGitHub Actionsを使ってレビュワーを割り当てる時はREST APIを使ってください

おわりに

 GitHub上でDependabotとGitHub Actionsを利用することによってソフトウェアの依存関係の更新を開発者の負担をなるべく減らしてできるようになりました。

 しかしこれは更新によって防げる場合のみなので、ライブラリ・パッケージの開発の更新が止まっている場合は対策できません。そのため、GitHub Alertsなどによって開発が止まった依存関係の脆弱性を検知できるようにする必要もあります。

 また、最新のパッケージに積極的に更新していくことが必ずしもセキュリティ的に一番良いという策にはならず、新しいバグや脆弱性が埋め込まれるという可能性もあります。バグについてはCIを充実させることによってある程度防ぐことが可能ですが、両者とも開発コミュニティなどで情報収集をして日々注意して開発する必要があります。

 そのほかにScalaついて、DependabotはScalaについて対応していません。そのため、Dependabotのみを使用している場合はScalaの依存関係を更新することができません。ですがScala Steward31などを利用することによってScalaでも依存関係を自動更新することができます。しかしGitHubでScala Stewardを利用するとGitHub Appを使用する必要があるため、もし使用できない場合は、Sbt Dependency Submission32とDependabot Alertsを組み合わせることによって脆弱性の検知ができるようになりますので、Scalaでも依存関係のセキュリティを健全に保ちたい場合はこれらのツールを検討してみてください。

参考


  1. 総務省|令和元年版 情報通信白書|ICTにより、新しい経済・社会の仕組みが生まれている, https://www.soumu.go.jp/johotsusintokei/whitepaper/ja/r01/html/nd101100.html
  2. ICT サイバーセキュリティ総合対策 2022, https://www.soumu.go.jp/main_content/000830903.pdf
  3. プラクティス・ナビ IPA 情報処理推進機構, https://www.ipa.go.jp/security/economics/practice/practices/Practice101
  4. Open Source Security and Risk Analysis 2023, https://www.synopsys.com/content/dam/synopsys/sig-assets/reports/rep-ossra-2023.pdf
  5. Understanding Open Source Adoption: Insights from the 9th State of the Software Supply Chain Report., https://www.sonatype.com/state-of-the-software-supply-chain/Introduction
  6. NVD - CVE-2021-44228, https://nvd.nist.gov/vuln/detail/CVE-2021-44228
  7. Apache Log4jに見つかった脆弱性「Log4Shell」とは | ドコモビジネス | NTTコミュニケーションズ 法人のお客さま, https://www.ntt.com/business/services/xmanaged/lp/column/apache-log4j.html
  8. Apache Log4j に関する解説 1.6版, https://www.intellilink.co.jp/-/media/ndil/ndil-jp/column/vulner/2021/121500/ApacheLog4j.pdf
  9. Secure Code Warrior Survey Finds 86% of Developers Do Not View Application Security As a Top Priority, https://www.securecodewarrior.com/press-releases/secure-code-warrior-survey-finds-86-of-developers-do-not-view-application-security-as-a-top-priority
  10. 2019 State of the Software Supply Chain, https://www.sonatype.com/hubfs/SSC/2019%20SSC/SON_SSSC-Report-2019_jun16-DRAFT.pdf
  11. Keeping your supply chain secure with Dependabot - GitHub Docs, https://docs.github.com/en/code-security/dependabot
  12. Mend Renovate Automated Dependency Updates | Mend.io, https://www.mend.io/renovate/
  13. GitHub, https://github.com/
  14. The DevSecOps Platform | GitLab, https://about.gitlab.com/
  15. Living Off Our Savings and Growing Our SaaS to $740/mo - Indie Hackers, https://www.indiehackers.com/interview/living-off-our-savings-and-growing-our-saas-to-740-mo-696f9b110f
  16. Acquired by GitHub!, https://www.indiehackers.com/product/dependabot/acquired-by-github--LgT7DN1rGEZM2O4srhF
  17. Goodbye Dependabot Preview, hello Dependabot! - The GitHub Blog, https://github.blog/2021-04-29-goodbye-dependabot-preview-hello-dependabot/
  18. Identifying vulnerabilities in your project's dependencies with Dependabot alerts - GitHub Docs, https://docs.github.com/en/code-security/dependabot/dependabot-alerts
  19. Automatically updating dependencies with known vulnerabilities with Dependabot security updates - GitHub Docs, https://docs.github.com/en/code-security/dependabot/dependabot-security-updates
  20. Keeping your dependencies updated automatically with Dependabot version updates - GitHub Docs, https://docs.github.com/en/code-security/dependabot/dependabot-version-updates
  21. Prioritizing Dependabot alerts with Dependabot auto-triage rules - GitHub Docs, https://docs.github.com/en/code-security/dependabot/dependabot-auto-triage-rules
  22. Introducing auto-triage rules for Dependabot - The GitHub Blog, https://github.blog/2023-09-14-introducing-auto-triage-rules-for-dependabot/
  23. About Dependabot alerts - GitHub Docs, https://docs.github.com/en/code-security/dependabot/dependabot-alerts/about-dependabot-alerts
  24. About the GitHub Advisory database - GitHub Docs, https://docs.github.com/en/code-security/security-advisories/working-with-global-security-advisories-from-the-github-advisory-database/about-the-github-advisory-database
  25. Configuration options for the dependabot.yml file - GitHub Docs, https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#open-pull-requests-limit
  26. About code owners - GitHub Docs, https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners
  27. Automating Dependabot with GitHub Actions - GitHub Docs, https://docs.github.com/en/code-security/dependabot/working-with-dependabot/automating-dependabot-with-github-actions#enable-auto-merge-on-a-pull-request
  28. Events that trigger workflows - GitHub Docs, https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request
  29. dependabot/fetch-metadata: Extract information about the dependencies being updated by a Dependabot-generated PR., https://github.com/dependabot/fetch-metadata
  30. gh pr edit --add-reviewer Don't acquire organizational teams if it's not necessary · Issue #4844 · cli/cli, https://github.com/cli/cli/issues/4844
  31. scala-steward-org/scala-steward: :robot: A bot that helps you keep your projects up-to-date, https://github.com/scala-steward-org/scala-steward
  32. scalacenter/sbt-dependency-submission: A Github Action to submit the dependency graph of an sbt build to the Dependency Submission API, https://github.com/scalacenter/sbt-dependency-submission