ANDPAD ボードと歩掛管理を開発しております soe-j です。 昨年、大量の npm パッケージに不正なコードが混入したり、Node.js のセキュリティアップデートが話題になったり、緊張感が高まってきています。
- 大量の人気パッケージにサプライチェーン攻撃が発生した件についての記事
https://www.aikido.dev/blog/npm-debug-and-chalk-packages-compromised - Node.js セキュリティアップデート情報
https://nodejs.org/ja/blog/vulnerability/
これらの状況から、ちょっとセキュリティ意識が高くなったのを機に、ライブラリのアップデート管理を Dependabot から Renovate へ移行しました。
なぜ Renovate へ移行したのか、具体的にどのような設定をしたのかについてご紹介します。
移行の背景
Dependabot から Renovate に移行した主な理由は、以下の 2 点です。
- パッケージごとに cooldown 設定をしたい
- 昨今の npm の治安を考慮して、一般的なパッケージには cooldown(待機期間)を設定したい
- 攻撃者が悪意あるコードを混入したパッケージを公開してから、セキュリティ研究者やコミュニティがそれを発見し、レジストリから削除されるまでには数時間〜数日のタイムラグがある
- この「発見されるまでの時間」を待つことで、悪意あるアップデートを取り込むリスクを減らす狙い
- 一方で、社内ライブラリは cooldown なしで即座にアップデートしたい
- Dependabot では、パッケージごとに異なる cooldown を設定できない
- 昨今の npm の治安を考慮して、一般的なパッケージには cooldown(待機期間)を設定したい
- Node.js バージョンアップデートを完全に自動化したい
- Node.js バージョンをあげる際、プロジェクト内のあらゆるファイルを一括で更新する必要があった
package.jsonのenginesフィールド.node-version- Dockerfile 上のイメージ指定
- CI 上の Node.js バージョン指定
- Dependabot で Dockerfile 上の image 指定のアップデートは可能だが、それ以外の更新は不可
- 複雑な作業ではないが毎度手作業が必要であり、チームとして対応するには手順書を作成するのか?という状況
- Node.js バージョンをあげる際、プロジェクト内のあらゆるファイルを一括で更新する必要があった
これらを解決すべく、Renovate への移行を始めました。
Renovate の設定
2つの移行の理由ごとに設定を紹介していきます。
パッケージごとに cooldown 設定
Dependabot において cooldown というオプションである「一定期間更新を待機する設定」は、
Renovate では minimumReleaseAge というオプションになっています。
この minimumReleaseAge を弊チームでは 3 days に設定しています。
こうすることで、リリースから 3 日経過したパッケージのみがアップデート対象となります。
この日数は「何日だから大丈夫」と言えるものではありませんし、具体的に何かを集計した結果ではないですが、
現状の世のコミュニティ対応状況からして、2 日か 3 日待てば十分と考えています。
{ "$schema": "https://docs.renovatebot.com/renovate-schema.json", "extends": [ "config:best-practices", ":timezone(Asia/Tokyo)", ":labels(renovate, dependencies)", ":enableVulnerabilityAlertsWithLabel(security)" ], "prConcurrentLimit": 30, "enabledManagers": [ "circleci", "github-actions", "docker-compose", "dockerfile", "npm", "nodenv" ], "minimumReleaseAge": "3 days", "lockFileMaintenance": { "enabled": true } }
パッケージごとの minimumReleaseAge 設定
社内ライブラリ(@88labs/*)については、グループを用意して、個別に minimumReleaseAge: null を設定しています。
これによりグローバル側の設定は無効化されて、社内ライブラリはリリース後すぐにアップデート対象となります。
{ "packageRules": [ { "groupName": "tsukuri", "matchManagers": ["npm"], "matchPackageNames": ["@88labs/tsukuri-*", "@88labs/*-tsukuri"], "minimumReleaseAge": null } ] }
ちなみに、この tsukuri というパッケージ群は、アンドパッドのデザインシステムです。
これで1つめの要件は達成できました。
Node.js バージョン管理の自動化
次に、Node.js バージョン管理の自動化についてですが、対応を簡単にするため少々前準備をしました。
Renovate での自動化に向けた前準備 〜更新対象ファイルの整理〜
Node.js のバージョンを司っている部分としては、主に以下の通りでした。
package.jsonのengines.nodeフィールド.node-versionファイルDockerfileのFROM node:XX.XX.XX-slimのイメージ指定
しかし、これに加えて、GitHub Actions 上で Node.js バージョンを指定している部分があり、それらもまとめて更新する必要がある状態でした。
Renovate では、それらをCustom Managerでアップデートもできますが、そもそも GitHub Actions 上でバージョン指定しなくて良い方法はないか模索しました。
最終的には actions/setup-node アクションに、.node-version を参照するオプションがあったので、それを活用することとしました。
共通アクションの作成
# .github/actions/setup-node/action.yml name: Setup Node.js description: Setup Node.js environment runs: using: composite steps: - name: Setup Node.js uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 with: node-version-file: .node-version
ワークフローでの使用例
各ワークフローでは、この共通アクションを使用するだけで、.node-version で指定された Node.js バージョンが自動的に使用されます。
jobs: lint: runs-on: ubuntu-24.04-2cores-arm64 steps: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Setup Node.js uses: ./.github/actions/setup-node - name: Install dependencies run: npm ci - name: Run lint run: npm run lint
これにより、更新が必要なファイルは以下の 3 つに集約されました。
package.jsonのengines.nodeフィールド.node-versionファイルDockerfileのFROM node:XX.XX.XX-slimのイメージ指定
Renovate での Node.js バージョン更新設定
Renovate では、これらの更新はすべて用意された manager で実現できます。 https://docs.renovatebot.com/modules/manager/
また、それらを 1 つのグループにまとめることで 1 つの PR にまとめることができます。 https://docs.renovatebot.com/configuration-options/#group
{ "packageRules": [ { "groupName": "Node.js", "matchManagers": ["npm", "nodenv", "dockerfile"], "matchPackageNames": ["node"], "matchDepNames": ["node"], "addLabels": ["node"] } ] }
この設定により、package.json の engines.node、.node-version、Dockerfile の Node.js イメージが更新される際、1 つの PR にまとめられます。
Renovate の manager は以下のように対応しています。
npm:package.jsonのengines.nodeを更新npmという指定だけではpackage.jsonの dependencies などもまとめて更新されてしまうmatchDepNamesmatchPackageNamesを使って、 engines.node のみを更新するように指定
nodenv:.node-versionファイルを更新dockerfile: Dockerfile のFROM node:XX.XX.XXを更新- こちらも
matchDepNamesmatchPackageNamesを使って、 node のイメージのみを更新対象に指定
- こちらも
注意点としては、新しい Node.js バージョンがリリースされた直後に、Docker イメージがまだ作成されていないことがあげられます。 このタイミングでは、Dockerfile の更新だけされてなかったり、バージョンがズレている PR が作成されることになります。 検知できるように CI などを用意しておくことをオススメします。
まとめ
これらの Renovate 設定により、アップデートタイミングのコントロール、Node.js バージョンアップの自動化が実現できました。
Renovate は非常に柔軟な設定が可能で、要件に合わせて細かくカスタマイズできます。Dependabot で実現できなかった要件がある場合は、Renovate への移行を検討してみてはいかがでしょうか。
アンドパッドでは Safety で効率的なアップデートを模索するエンジニアを歓迎しています。