こんにちは、SaaS事業部の鐘川です。
しばらく使用されずに放置されていた社内システムが最近また必要になったので、そのシステムの一部であるReactを使用したフロントエンドアプリのNode.jsバージョンアップ対応を行いました。今回はその対応中に起こったエラーの解消方法やバージョンアップ後のパッケージの脆弱性対応などの手順をまとめていきます。
本記事の内容はmacでの操作を想定しています。
環境
Node.jsのパッケージ管理にはyarnを、バージョン管理にはvoltaを使用しています。
- OS: mac
- Node.js: v10.21.0 → v18.12.1
- yarn: v1.22.19
- volta: v1.0.8
Node.js 18にバージョンアップ
現在のNode.jsのバージョンを確認
バージョンアップ前のNode.jsのバージョンはv10.21.0
でした。
$ node -v v10.21.0
Node.js 18のインストール
voltaを使用してインストール
voltaは簡単に使用できるNode.jsのバージョン管理ツールです。
voltaを使用することでプロジェクト毎にNode.jsのバージョンを自動的に切り替えることが可能になります。
voltaを導入する場合は公式ドキュメントを参考にしてください。
voltaを使用して当時最新LTSであったv18.12.1
をインストールしました。
$ volta install node@18.12.1
Node.jsのバージョンを固定
voltaでNode.jsのバージョンを固定します。バージョンを固定しておくことで、プロジェクト内でのバージョン管理が楽になります。
Node.jsのバージョンを先ほどインストールした v18.12.1
に固定します。
$ volta pin node@18.12.1 success: pinned node@18.12.1 in package.json
コマンドを叩くとpackage.json
も更新されます。
package.json
"volta": { "node": "18.12.1" }
再度Node.jsのバージョンを確認すると、v18.12.1
に更新されているのが確認できました。
$ node -v v18.12.1
OpenSSLの互換性エラー対策
Node.js 17から、OpenSSLのバージョンが以前までのv1.1.0
からv3.0.0
に更新されました。
これが原因でOpenSSLのバージョンと互換性が取れず、エラーが発生することがあります。
当アプリケーションでも以下のようなエラーが発生しました。
Error: error:0308010C:digital envelope routines::unsupported
このエラーを解消するためには、以下のように環境変数を指定します。
--openssl-legacy-provider
オプションを使用することでOpenSSL3.0レガシー・プロバイダーが有効になります。
$ export NODE_OPTIONS=--openssl-legacy-provider
Node.js 18へのバージョンアップ後、アプリケーションを起動しようとすると、
$ yarn start
エラーが発生しました。
Error: Node Sass does not yet support your current environment:
原因はCSSメタ言語であるSassのコンパイルを行うパッケージであるnode-sass
のバージョンが古く、Node.js 18に対応していないことでした。
node-sass
のバージョンアップをしようとも思いましたが、調べたところ公式ではnode-sass
は2020年10月から非推奨となっており、現在はdart-sass
が推奨されています。
この機会にnode-sass
からdart-sass
への移行を行いました。
node-sassをdart-sassに移行
dart-sassのインストール
node-sass
をアンインストールしてdart-sass
をインストールします。
ややこしいですが、dart-sass
のパッケージ名はsass
です。
$ yarn remove node-sass $ yarn add sass
v1.58.3
がインストールされました。
$ yarn list --depth=0 | grep sass ├─ postcss-sass@0.4.4 ├─ sass-loader@6.0.7 ├─ sass@1.58.3
node_modulesを再インストール
Node.jsのバージョンアップが原因で、パッケージに不具合が発生する可能性や依存関係が変更される恐れがあるため、一度node_modules
を再インストールします。
依存関係を新しく更新したいのでyarn.lock
は削除。古いままの依存関係がキャッシュされている可能性があるので、yarnのキャッシュも削除しておきます。
package.json
があればyarn.lock
は一度削除してもyarn install
実行完了時に再生成されますが、問題があった場合にプロジェクトを復元できるようにバックアップをとっておくことをお勧めします。
$ yarn cache clean $ rm -rf node_modules yarn.lock $ yarn install
再度アプリケーションを起動しようとすると、
$ yarn start
別のエラーが発生。
Module build failed: Error: Cannot find module 'node-sass'
原因はSassをCSSに変換するパッケージであるsass-loader
のバージョンが古く、dart-sass
に対応していなかったためでした。
sass-loader
のバージョンが古い場合はnode-sass
が必須であり、そのnode-sass
を削除したため、エラーが発生しています。
v7.1.0
以降ではdart-sass
に対応し、node-sass
またはdart-sass
のどちらかが必須となっています。
sass-loaderのバージョンアップ
sass-loader
のバージョンを確認すると、v6.0.7
でした。
$ yarn list --depth=0 | grep sass-loader ├─ sass-loader@6.0.7
これをv7.3.1
にアップグレードします。
さらに新しいバージョンもありますが、v7.3.1
より新しいバージョンを指定するとパッケージの依存関係が壊れてアプリケーションが正常に動作しないことが確認できたため、依存関係が壊れない範囲の最新バージョンであるv7.3.1
を指定しています。
$ yarn upgrade sass-loader@7.3.1
その後yarn start
でアプリケーションの起動が成功しました!
パッケージの脆弱性解消
Node.js 18でのアプリケーション起動は成功しましたが、併せて行いたいのがパッケージの脆弱性解消です。 脆弱性を解消しておくことで、より安全にアプリケーションを運用することができます。
脆弱性のあるパッケージ調査
yarnを使用している場合はyarn audit
で脆弱性のあるパッケージを調査できます。
今回は--groups dependencies
と指定することで、アプリケーション実行時に使用するパッケージに絞り込んで脆弱性対応を行います。
$ yarn audit --groups dependencies ... ┌───────────────┬──────────────────────────────────────────────────────────────┐ │ critical │ Prototype Pollution in minimist │ ├───────────────┼──────────────────────────────────────────────────────────────┤ │ Package │ minimist │ ├───────────────┼──────────────────────────────────────────────────────────────┤ │ Patched in │ >=0.2.4 │ ├───────────────┼──────────────────────────────────────────────────────────────┤ │ Dependency of │ multer │ ├───────────────┼──────────────────────────────────────────────────────────────┤ │ Path │ multer > mkdirp > minimist │ ├───────────────┼──────────────────────────────────────────────────────────────┤ │ More info │ https://www.npmjs.com/advisories/1091172 │ └───────────────┴──────────────────────────────────────────────────────────────┘ 184 vulnerabilities found - Packages audited: 749 Severity: 11 Low | 48 Moderate | 99 High | 26 Critical
コマンドを叩くと、脆弱性のあるパッケージ情報一覧と最後に内訳が表示されます。
脆弱性レベルはLow
Moderate
High
Critical
の4段階に分類され、脆弱性が見つかった場合にはパッケージ更新を検討します。
今回は749個のパッケージ中、184件の脆弱性が見つかる結果となりました。
脆弱性解消
以下の方法で脆弱性解消を行っていきます。
- yarn upgradeでパッケージを更新して脆弱性解消
- 依存関係を調査して大元にあるパッケージを更新
- 依存パッケージのバージョンを指定
1. yarn upgradeでパッケージを更新して脆弱性解消
yarn upgrade
を実行すると、package.json
に記述されたバージョンの範囲でパッケージを更新します。
yarn upgrade --latest
と実行した場合はpackage.json
を無視してパッケージを最新バージョンに更新するため脆弱性解消は見込めますが、更新後のパッケージに破壊的変更が加わっていた場合はアプリケーションが正常に動作しなくなる恐れがあります。
$ yarn upgrade
再度脆弱性調査を行うと、
$ yarn audit --groups dependencies ... 36 vulnerabilities found - Packages audited: 643 Severity: 2 Low | 17 Moderate | 14 High | 3 Critical
脆弱性件数が184件から36件にまで減少しました。 総パッケージ数が749個から643個に減っていることから、不要なパッケージが削除されていることも確認できます。
2. 依存関係を調査して大元にあるパッケージを更新
yarn upgrade
を実行しても脆弱性が解消できなかった36個のパッケージについてはpackage.json
で指定されたバージョンの範囲が古いことや、パッケージの依存関係が深く、バージョン更新が行えなかったことが考えられます。これらのパッケージについては依存関係を調査し、その大元にあるパッケージを更新して脆弱性が解消されるか確認します。
例として今回脆弱性が発見されたminimist
の更新を行います。
$ yarn audit --groups dependencies ... ┌───────────────┬──────────────────────────────────────────────────────────────┐ │ critical │ Prototype Pollution in minimist │ ├───────────────┼──────────────────────────────────────────────────────────────┤ │ Package │ minimist │ ├───────────────┼──────────────────────────────────────────────────────────────┤ │ Patched in │ >=0.2.4 │ ├───────────────┼──────────────────────────────────────────────────────────────┤ │ Dependency of │ pm2 │ ├───────────────┼──────────────────────────────────────────────────────────────┤ │ Path │ pm2 > mkdirp > minimist │ ├───────────────┼──────────────────────────────────────────────────────────────┤ │ More info │ https://www.npmjs.com/advisories/1091172 │ └───────────────┴──────────────────────────────────────────────────────────────┘ ... 36 vulnerabilities found - Packages audited: 643 Severity: 2 Low | 17 Moderate | 14 High | 3 Critical
パッケージの依存関係をyarn audit
の結果から調査します。
Dependency of
の行を見るとminimist
の依存関係の大元にあるのはpm2
であることが確認できます。
既にyarn upgrade
は実行しているので、pm2
はpackage.json
に指定された範囲内での最新バージョンのはずです。package.json
とyarn.lock
を確認してみます。
package.json
"dependencies": { ... "pm2": "^2.9.1", ... }
package.json
には"pm2": "^2.9.1"
と記述されています。
^
(キャレット)が指定されている場合は、「一番左の0以外の数字を変更しない最新バージョン」を意味するので、今回のpm2
の場合はv2.9.1 <= version < 3.0.0
の範囲の最新となります。^
以外にも~
(チルダ)や不等号なども指定できます。
yarn.lock
pm2@^2.9.1: version "2.10.4" resolved "http://registry.yarnpkg.com/pm2/-/pm2-2.10.4.tgz#dd292fd26aed882f6e9f7b9652191387d2debe6a" integrity sha512-AuAA6DoF/R3L9zSuYtKzaEd6UFvhCKqfW49dgLe0Q4SQtYmQMmXmyEAp5tr1iduJrqGRwpb5ytVm2rWZ56/4Vg== dependencies: async "^2.5" blessed "^0.1.81" chalk "^1.1" chokidar "^2" cli-table-redemption "^1.0.0" commander "2.13.0" cron "^1.3" debug "^3.0" eventemitter2 "1.0.5" fclone "1.0.11" mkdirp "0.5.1" ...
yarn.lock
を確認すると、実際にインストールされているpm2
のバージョンはv2.10.4
であることが確認できました。公式を確認するとpackage.json
で指定したv2.9.1 <= version < 3.0.0
の範囲の最新はv2.10.4
だったので、依存関係が深くてバージョン更新に失敗したわけではなく、package.json
で指定したバージョン範囲の最新に更新はされているが、その範囲指定が古かったことが脆弱性解消できなかった原因だと分かります。この場合はpackage.json
の範囲指定を超えて更新を行う必要があります。
$ yarn upgrade pm2 --latest
--latest
を指定して、package.json
を無視して最新バージョンへ更新します。
このとき、パッケージの公式ドキュメントを見て、バージョンアップ後に破壊的変更がされていないかの確認は必須です。破壊的変更があった場合は、yarn upgrade pm2 @バージョン数
のようにバージョンを指定して破壊的変更がないバージョンへ更新、もしくは別パッケージへの移行を検討します。
今回のpm2
は最新バージョンへの更新で問題なく、v5.2.2
に更新されました。
再度脆弱性調査を行うと、minimist
の脆弱性が解消されているのが確認できました。
脆弱性件数は36件から31件に減少しています。
$ yarn audit --groups dependencies ... 31 vulnerabilities found - Packages audited: 593 Severity: 2 Low | 15 Moderate | 12 High | 2 Critical
3. 依存パッケージのバージョンを指定
依存関係の大元にあるパッケージを更新しても脆弱性が解消しない場合や、破壊的変更などがあり、大元のパッケージを更新できない場合はpackage.json
にresolutions
を指定することで、依存パッケージのバージョンを固定することが可能です。
ただし強制的にバージョンを指定するため、依存関係が壊れないか、アプリケーションへの影響はないかの確認を慎重に行う必要や、今後依存関係が更新された場合にはresolutions
でバージョンを指定したパッケージについては手動で依存関係を更新する必要があります。
先ほどのminimist
の脆弱性をこの方法で解消してみます。yarn audit
実行結果のPatched in
の行を見ると、どのバージョンを指定すれば脆弱性が解消できるか確認できます。>=0.2.4
とあるので、v0.2.4
以上のバージョンを指定します。依存関係はパッケージ名を/
で区切って指定できます。
package.json
... "dependencies": { ... }, "resolutions": { "pm2/mkdirp/minimist": "^0.2.4" }
その後yarn install
、yarn audit
を実行してminimist
の脆弱性が解消されていることが確認できました。
他のパッケージについてもこれらの方法で脆弱性対応を行いました。
終わりに
今回はNode.jsのバージョンアップ、パッケージの脆弱性対応を行いました。
これからNode.jsのバージョンアップを検討している方のお役に立てれば幸いです。