Sansan Tech Blog

Sansanのものづくりを支えるメンバーの技術やデザイン、プロダクトマネジメントの情報を発信

Vol.07 Dependabotをgradle monorepo構成のBill Oneに導入した話

はじめに

Bill One 開発 Unit ブログリレー2024の第7弾になります!

技術本部 Bill One Engineering Unit 兼 情報セキュリティ部 CSIRTグループの茂木です。
現在は「かにかん」という名前のチームに所属しており、PdL1という役割を担っています。
日々プロダクト開発だけでなく、CSIRT2兼務者としてBill Oneのセキュリティ向上にも取り組んでいます。

今回は、セキュリティ向上施策の1つとしてBill OneにDependabotを導入して脆弱性を管理できるようにした話、Bill Oneの技術構成だからこそ起きた問題について紹介します。なお、本記事はSansan Advent Calendar 2024の21日目の記事です。

目次

背景

Sansanでは、今年よりCSIRTと連携して、組織のソフトウェア開発プロセスにおけるセキュリティ成熟度の評価・改善の取り組みを行っています。
評価には、OWASPが公開しているSoftware Assurance Maturity Model (SAMM)フレームワークを活用しており、その指標の1つにパッチとアップデートに関する項目があります。

Bill OneではRenovateを利用し、月に1度アップデートウィークという期間を設け、全員でBill Oneに関わるライブラリのバージョンアップを行っています。
また、CSIRT/Bill Oneエンジニアも日々情報収集をしており、プロダクトに関係しそうな脆弱性が発見された場合は調査を行っています。

脆弱性に対して最低限の対応は行っていたため、パッチ適用やアップデートに関する取り組みについて一定の評価は得られました。 しかし、現在のプロセスでは月に一度のアップデートや個々の情報収集力に依存しており、更に高いレベルを目指すために脆弱性の対応状況をリアルタイムでトラッキングする仕組みを整えた方が良いと考えました。

そこで、SansanのプロダクトであるEightでRenovateとDependabotを併用している事例を参考に、Bill OneでもDependabotを導入しました。

Dependabotとは

DependabotはGitHubの機能として提供されているリポジトリ内の依存関係をチェックし、依存しているライブラリの脆弱性を通知したり、バージョンアップのPRを作成したりしてくれる機能です。
Dependabotには大きく分けて3種類あるようで、

  • Dependabot alerts
    (システムが利用しているライブラリに脆弱性の報告がされている場合、開発者に通知してくれる機能)
  • Dependabot security updates
    (システムが利用しているライブラリにセキュリティアップデートがある場合、自動でPRを作成してくれる機能)
  • Dependabot version updates
    (システムが利用しているライブラリのバージョンアップがある場合、自動でPRを作成してくれる機能)

があります。

Dependabot version updatesは利用しておらず、ライブラリのアップデートはRenovateで行っています。 今回は、Dependabot alertsとDependabot security updatesを活用し、脆弱性の対応状況をリアルタイムでトラッキングできる状態をゴールにしました。

Dependabot alertsの導入

Dependabot alertsを導入するのはとても簡単です。
RepositoryのSettings -> Code security and analysisからDependency graphとDependabot alertsを有効にするだけです。

ですが、Bill Oneでは2つの問題がありました。
1つ目は、Bill Oneというプロダクトに関わるリポジトリは

  • Frontend
  • Backend
  • Google Cloud Functions用
  • 社内の人用の管理システム

と4つあり、それぞれのリポジトリでDependabot alertsを確認する必要があります。
また、ステータスの管理や即時通知ができないため、運用していく上で、リポジトリ横断で包括的に可視化やステータス管理、脆弱性が発見された際にすぐに必要な情報を通知する仕組みが必要でした。
※GitHub Enterpriseを導入すれば包括的に管理できる機能があるようですが、本取り組み時点では導入していません。

2つ目は、なぜかgradleの依存関係を認識してくれないという問題です。
GitHubにはDependency graphという機能があり、リポジトリの依存関係を可視化できる機能があります。
しかし、gradleで管理している依存関係がDependency graphに表示されないという事象が発生しました。

Bill OneのBackendリポジトリはmonorepo構成で管理しており、Kotlin / Go / Node.jsが混在しています。
元々Dependabotとgradleはあまり相性が良くないようで、monorepo構成であることも相まって発生していると予想していました。

解決策 1: Dependabot alertsをSpreadSheetで一元管理

Dependabot alertsはリポジトリごとでしか見ることができないため、GASを使ってSpreadSheetで管理できるようにしました。
CSIRTからDependabot alertsのGitHub APIがあることは事前に教えてもらっていたので、愚直に毎日朝に取得して新規分はSpreadSheetに追加、既存分は更新するようにしました。
これにより、Bill Oneに関わるリポジトリの脆弱性を一覧で見られるようになりました。

また、新規分があった場合はSlackへ通知するようにしました。

※大まかなコードは次の通りです。

const main = () => {
  const sheet = SpreadsheetApp.openById("").getSheetByName("Dependabot");

  const repoList = [
    "bill-one-monorepo",
    "bill-one-frontend",
    "bill-one-functions",
  ];

  const rowMap = sheet
    .getDataRange()
    .getValues()
    .forEach((row, index) => {
      // 既存のデータを扱いやすいようにMapに変換
    });

  repoList.forEach((repo) => {
    const alerts = getDependabotAlerts(repo);
    alerts.forEach((alert) => {
      const targetRowInfo = rowMap[key];
      if (targetRowInfo) {
        // 既存データの更新
      } else {
        addAlertRow(sheet, repo, alert);

        const message = `
          <!channel>
          以下のAlertが新規追加されました。
            Repository: ${repo}
            Number: ${alert.number}
            Severity: ${alert.security_advisory.severity}
            CVE: ${alert.security_advisory.cve_id}
            管理表のURL: https://xxxxxxx
        `;
        postSlack(message);
      }
    });
  });
};

const getDependabotAlerts = (repo) => {
  const url = `https://api.github.com/repos/[organization名]/${repo}/dependabot/alerts`;
  const options = {
    method: "get",
    headers: {
      Authorization: `token xxxxx`,
      Accept: "application/vnd.github.v3+json",
    },
    muteHttpExceptions: true,
  };

  const response = UrlFetchApp.fetch(url, options);
  const json = response.getContentText();
  const data = JSON.parse(json);

  return data;
};

const postSlack = (message) => {
  UrlFetchApp.fetch("https://hooks.slack.com/xxxxx", {
    method: "post",
    contentType: "application/json",
    payload: JSON.stringify({ text: message }),
  });
};

const addAlertRow = (sheet, repo, alert) => {
  const row = [
    repo,
    alert.number,
    alert.dependency.package.ecosystem,
    alert.dependency.manifest_path,
    alert.dependency.package.name,
    alert.security_advisory.ghsa_id,
    alert.security_advisory.cve_id,
    alert.security_advisory.summary,
    alert.security_advisory.description,
    alert.security_advisory.severity,
    alert.html_url,
    alert.fixed_at,
    alert.created_at,
    alert.updated_at,
  ];
  sheet.appendRow(row);
};

解決策 2: 専用のGitHub Actionsの導入でDependabotにgradleの依存関係を認識させる

gradleがDependabotに正しく認識されず困っている記事も読んで同じ対応をしたのですが、解決できませんでした。
困っていたところCSIRTからGradle partners with GitHub on supply chain securityという記事を教えてもらいました。
※ちゃんとドキュメントを確認したところ、依存関係送信APIを使用しなければいけない旨の記載がありました。(Dependabotのエコシステムのサポート | gradle)

gradleに関してはGitHubのmainブランチの変更があるたびに記事の通りdependency-submissionすることで、Dependabot が正しく認識するようになりました。

name: Dependency Submission

on:
  push:
    branches: ["main"]

permissions:
  contents: write

jobs:
  dependency-submission:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout sources
        uses: actions/checkout@v4
      - uses: actions/setup-java@v4
        with:
          distribution: "temurin"
          java-version: "21"
      - name: Generate and submit dependency graph for serviceA
        uses: gradle/actions/dependency-submission@v3
        with:
          build-root-directory: "bill-one-service-a"
      - name: Generate and submit dependency graph for serviceB
        uses: gradle/actions/dependency-submission@v3
        with:
          build-root-directory: "bill-one-service-b"

その後の調査で、lockfileがないため依存関係送信APIを利用する必要があるのではないかという仮説を立てました。
Bill OneではgradleのLocking Versionsを利用しておらず、build時に依存関係の解決処理がされ、Bill Oneが利用しているライブラリが利用しているライブラリのバージョンが確定します。
コード上では依存関係の全てのバージョンを確認する方法がないため、依存関係送信APIを利用してDependabotに伝えてあげる必要があるのだと思います。
Support Gradle lockfiles

Dependabot security updatesの導入

解決策 1, 2 により、Dependabot alertsの導入と運用ができるようになり、脆弱性の管理ができるようになりました。
次に脆弱性を解消するための仕組みとして、Dependabot security updatesを導入しました。

./github/dependabot.ymlに設定を記述することでDependabot security updatesを導入できます。

version: 2

updates:
  - package-ecosystem: "gradle"
    directories:
      - "/bill-one-service-a"
      - "/bill-one-service-b"
    schedule:
      interval: "daily"
      time: "09:00"
      timezone: "Asia/Tokyo"
    labels:
      - "security-updates"
    open-pull-requests-limit: 0
    groups:
      security-update:
        applies-to: security-updates
        patterns:
          - "*"
  - package-ecosystem: "npm"
    directories:
      - "/bill-one-service-c"
    schedule:
      interval: "daily"
      time: "09:00"
      timezone: "Asia/Tokyo"
    labels:
      - "security-updates"
    open-pull-requests-limit: 0
    groups:
      security-update:
        applies-to: security-updates
        patterns:
          - "*"
  - package-ecosystem: "gomod"
    directories:
      - "/bill-one-service-d"
    schedule:
      interval: "daily"
      time: "09:00"
      timezone: "Asia/Tokyo"
    labels:
      - "security-updates"
    open-pull-requests-limit: 0
    groups:
      security-update:
        applies-to: security-updates
        patterns:
          - "*"

導入してみて

当初の問題である脆弱性の対応状況をリアルタイムでトラッキングできない件については、Dependabot alertsとSpreadSheetを組み合わせて管理できるようになりました。
また、新規の脆弱性はSlackで通知されるようになり、Bill Oneエンジニア / CSIRTで一つひとつ緊急度の判断ができるようになりました。
これにより、Bill Oneのセキュリティリスクコントロールを一段高められました。

Dependabot alertsのAPIで検知日と解消日が取れるため、アップデートウィークで大量の脆弱性が解決されていることが可視化できたのも良かったです。

Dependabot alerts / Dependabot security updatesの導入はものすごく簡単なのですが、自分たちのやり方に合わせて運用できるようにするまでは結構手間がかかりました。

今後

現在は新規の脆弱性が発見されるたびに緊急度の判断をしており、それなりの運用負荷がかかっています。
今後はトリアージ基準を作り、優先度判断をもう少し自動化できるようにしたいと考えています。

また、テストが十分されている部分に関しては自動的にリリースされる仕組みを作りたいと考えています。

宣伝

Bill One 開発 Unit ブログリレー2024はまだまだ続きます!
ブログリレーの最新の投稿について@SansanTechでお知らせしますので、ぜひフォローしてください。


  1. PdLについては自律的なチームを支える仕組み「3L」を参照してください
  2. Computer Security Incident Response Teamの略称で、セキュリティインシデントが発生した際に対応するチームのこと

© Sansan, Inc.