🫀

Dependabot alerts を 0 でキープしたい

2024/12/09に公開

こんにちは!ツクリンクエンジニアの oieioi です。この記事では Dependabot の警告の検知と対応を即時でできるようにするために何をしたかを紹介します。

Dependabot は便利

GitHub には Dependabot という機能があり、以下のことをやってくれます。

  • npm や RubyGems などのパッケージシステムの脆弱性情報を収集する
  • 自身のリポジトリで使っているパッケージを監視し、脆弱性のあるパッケージを見つけると警告をだす
  • パッケージのアップデートを行うプルリクエストを作る

たとえば npm の場合は package-lock.jsonyarn.lock、RubyGemsの場合は
Gemfile.lock を見張って、警告を出したりプルリクエストを作ってくれたりします。

脆弱性情報の収集はパッケージ管理システムごとにやってるのを統合してるようです。RubyGems の場合は ruby-advisory-db をソースの一つとして取り込んでいるので、Dependabot を採用している場合は bundler-audit を回す必要はなくなります。 see also Dependabot でサポートされているエコシステムとリポジトリ

ユーザが多くてメンテナンスも頻繁なメジャーなライブラリの脆弱性はこいつを見てるだけでだいたい対応できます。すごく便利で安心。

Dependabot alerts を放置してませんか?

Dependabot alerts が放置され、警告があることが常態化していませんか。これは怖い状態です。メジャーなライブラリの脆弱性はすぐに攻撃されるし、警告にたいして鈍感になり、緊急で対応すべきものを見逃します。

警告がない状態を基本にして、警告がある状態に敏感になりたい。

怖い状態

Dependabot alerts に敏感になるためにやること

まず既存の警告を0にします。その後で Dependabot alerts が出たらそれを検知し、対応する体制を整えます。

めざすフロー。これをいつでもできる状態にする。

既存の警告を0にする

既存の警告を0にすることを目指します。

すべての警告が致命的で、すべての対応をすぐにやらなくちゃいけないということはほとんどありません。アプリケーションによっては対応する必要のない警告が出ている場合もあると思います。なので、まずは危険度、すなわちいつまでに対応すべきか、で分類を行います。後回しにできるものは後回しにしましょう。

例えばこんな感じで分けます。

危険度 対応期限 条件・例
critical すぐやる - システムをストップさせられる
- 個人情報など秘匿情報を不正に取得できるetc
high 今月中にやる - 攻撃の条件が限られる
-- システムユーザ権限を保持している時のみで攻撃できる
- 攻撃されても致命的な不具合が起きないetc
moderate 半年以内にやる - 開発環境のみで使用されているetc
なし やらない - そもそも使っていないetc

脆弱性の情報や脅威度(severity)は Dependabot alerts が提供してくれます。それぞれの警告を読むと以下のことがわかると思います。

  • どのライブラリ、どのバージョンか
  • どんな脆弱性か、どのような脅威があるのか
  • どのような回避方法があるのか

どんな脆弱性か理解できたら、自分のアプリケーションでそのライブラリの使用箇所をチェックします。

  • なぜそのライブラリを使っているのか
    • アプリケーションが直接依存しているのか、間接的に依存しているのか
      • 間接的な依存は調べるのが大変ですが、ツールがあります。
        • yarn-why
        • npm-explain
        • bundler は公式が提供しているツールがないですが bundler-why がシンプルで便利
          • もしくは Gemfile.lock をチェック
  • 脆弱性のある機能を使っているのか
    • 攻撃された場合、最悪どのくらいの影響があるのか
    • 攻撃は容易なのか
    • どのくらい使われている機能か

ここまで調べたらその脆弱性のヤバさが理解できると思うので、危険度(いつまでにやるか)を判断して分類します。

我々のチームではスクラム開発を採用していますが、criticalはインシデントと同じようにすぐ対応し、それ以外の場合はチケット管理システムにチケットを発行してスプリントプランニングで優先度付けを行う通常の開発フローにのせます。(優先度の高いはずのチケットがいつまで経っても対応できないという問題がある場合は開発フローの見直しが必要ですが、ここでは扱いません)

警告は、脆弱性のあるライブラリのバージョンが使われなくなると自動でクローズされます。また、通常の開発フローに乗せることができたhigh, moderateな警告は「対応がスタートした」とみなして Dismiss します。

これを繰り返すと警告を 0 にできます。

コツは、最悪のケースを想定すること、後回しにできるものは後回しにすること、「後回しにしていい」と判断した場合にその判断は正しいのか他の開発者にレビューしてもらうことです。

ライブラリのアップデート

脆弱性のあるバージョンをアップデートするだけですが大変です。以下のような手順で行っています。

  1. 脆弱性レポートを読む
    • Dependabot alerts が提供してくれます。
    • 内容
    • 攻撃方法
    • 影響範囲
  2. 対象のライブラリの CHANGELOG を読む
    • 対象の脆弱性対応以外にも変更がある場合がある
  3. 自身のアプリケーションで対象のライブラリを使っている箇所を読む
    • 対象のライブラリはどうやって使われてるか
      • 直接使われているのか、間接的に使われているのか
      • 脆弱性のある機能は使われているか
      • 攻撃可能か
  4. テスト書く
    • ピンポイントで使用箇所のテストがかけてたら良いけど、そうでなかったら書く。
    • 自動が最高だけど手動でもいい。
    • 実際の攻撃例もテストできたら最高
  5. アプデする
    • Dependabot の機能でPRが自動で作られます。(失敗している場合もある)
    • もしくはパッケージシステムのやりかたに従ってアップデートする。
      • bundle update <package_name> とか。
    • メジャーアップデートが必要になると破壊的な変更が出てくるのですごく大変になる。
    • バージョンアップなしで脆弱性があるコードにパッチを当てる方法が紹介されている場合もあるので、脆弱性レポートはちゃんと読むこと
  6. テストする
  7. リリースする

新しくでた警告を検知、対応する

検知

Dependabot alerts を GitHub の通知設定でONにします。(基本)

GitHub の通知だけだと忘れるので、業務で必ず見る Slack でうるさく通知するように GitHub Actions に定時処理を設定します。 Dependabot alertsを取得する API が公開されているので、これを利用して放置された alerts があるときは通知するようにします。

設定例

remind-depentabot-alerts.yml
name: Remind Dependabot alerts
on:
  schedule:
    # 好きな間隔で設定
    - cron: '0 1/6/9 * * *'
  # 手動
  workflow_dispatch:

jobs:
  dependabot-notifier:
    runs-on: ubuntu-latest
    steps:
    - name: Generate a token
      id: generate_token
      uses: actions/create-github-app-token@v1
      with:
        app-id: ${{ secrets.<YOU_GHA_APP_ID> }}
        private-key: ${{ secrets.<YOU_GHA_SECRET_KEY> }}
    - name: GitHub CLI
      env:
        GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }}
      # jqの整形は各自適当にやってください
      run: |
        gh api '/repos/<your_org>/<your_repository>/dependabot/alerts?state=open' \
           --jq \
           'map({
              c: .created_at,
              e: .dependency.package.ecosystem,
              n: .dependency.package.name,
              s: .security_vulnerability.severity
            })
            | unique_by(.s, .e, .n)
            | sort_by(.c)
            | reverse[]
            | "\(.s) on (\(.c)):  \(.e) の \(.n)"
           ' > vuls.txt || true

    - name: cat result
      run: cat vuls.txt

    - name: Extract Vulnerabilities info
      id: vulunabilities-result
      run: |
        echo "VULNERABILITIES<<EOF" >> "$GITHUB_OUTPUT"
        echo "$(cat vuls.txt)" >> "$GITHUB_OUTPUT"
        echo "EOF" >> "$GITHUB_OUTPUT"

    - name: 脆弱性情報をSlackに通知
      if: steps.vulunabilities-result.outputs.VULNERABILITIES != ''
      id: slack-failed
      env:
        VULNERABILITIES: ${{ steps.vulunabilities-result.outputs.VULNERABILITIES }}
        SLACK_BOT_TOKEN: ${{ secrets.<YOUR_SLACK_BOT_TOKEN> }}
      uses: slackapi/slack-github-action@v1.24.0
      with:
        channel-id: '<YOUR_SLACK_CHANNEL_ID>'
        slack-message: "Dependabot alerts を確認してください。 \n${{ env.VULNERABILITIES }}\nhttps://github.com/<your_org>/<your_repository>/security/dependabot"

(補足)
GitHub Actions がデフォルトで持っている secrets.GITHUB_TOKEN だとどんな permissions を付与しても dependabot/alerts API がエラー("Resource not accessible by integration")となり取得できなかったので、適切な権限を付けた GitHub App を作って API のリクエストを行う必要があった。 dependabot/alerts API がベータ機能だからっぽく、将来的には secrets.GITHUB_TOKEN そのままで取得できるようになるかも。(see also https://github.com/orgs/community/discussions/60612 )

対応

alertsを検知したら、緊急度を判断し、対応します。「既存の警告を0にする」と同じことをやればいいです。 critical な場合は即対応しましょう。

Dependabot alerts を日々対応することで得られたもの

地道にやるしかないですが、素晴らしいものが得られました。

  • メジャーな脆弱性は潰れているという安心感
  • 新しい警告にすぐ対応できる環境
    • alertsの発出から対応/dismiss までほぼ即日で対応できるようになりました。
  • バージョンアップを気軽にやろうという気持ち
    • 脆弱性の対応はだいたいマイナーバージョンアップだけど、日々これをやってるとメジャーアップデートもしやすくなるはず。

TODO

発展的内容としてDependabotの自動トリアージなどがある。危険度によって自動でアラートを無視したりスヌーズしたりする設定

Discussion