はじめに
OSS管理、特にSBOM(ソフトウェア部品表)に関係して「パッケージマネージャーを使用していると直接使っていないOSSも組み込まれるため管理が必要」と言われることがあります。
この「直接使っていないOSS」とは一体どういう意味でなぜ組み込まれるのでしょうか。これを理解するためにはパッケージマネージャーの仕組みをある程度理解する必要があります。
パッケージマネージャーとは
パッケージマネージャーとはその名のとおりパッケージを管理するソフトウェアやシステムのことです。一言でパッケージマネージャーと言っても色々ありますが、大まかには以下の二種類に分けられます。
- OSレベルの機能やソフトウェアパッケージを管理するもの
- ソフトウェア開発時に使用する機能部品や実行部品を管理するもの
前者はLinuxで言えばyumやaptなどが該当します。後者は各種開発言語・フレームワーク用に開発環境や実行環境を簡単に構築・管理するためのものでNode.jsのnpmやJavaのMavenなどが該当します。
ここでは後者のソフトウェア開発時に使用するパッケージマネージャーを取り上げます。
パッケージマネージャーの機能
多くのパッケージマネージャーは以下のような機能を提供しています。
(1)パッケージ配布サーバーの公開
多くのOSS開発言語/フレームワークではインターネット上にリポジトリと呼ばれる配布サーバーが公開され、各種の機能を実現するOSSがパッケージとして配布されています。パッケージマネージャーはネットワークを通じて必要なパッケージを取得することができます。
なおパッケージマネージャーが管理するパッケージは商用製品の場合もありますが、以後の説明ではOSSとして提供されているパッケージを対象とします。
(2)インストール管理
パッケージのインストールやアンインストール、情報管理、バージョンアップ手段の提供などを行います。
(3)依存関係の解決
一般的にあるパッケージを動作させるための前提条件として別のパッケージが必要となる場合があります。これを依存関係と呼びます。パッケージマネージャーはこの依存関係を管理しており、必要に応じてインストールやバージョンアップなどを行います。
ほかにもパッケージマネージャーに依りますが脆弱性情報やEOL(更新期限切れ)情報の提供など、色々な機能が提供されます。
冒頭の「直接使っていないOSS」は 「(3)依存関係の解決」が関係しています。
パッケージマネージャーによる依存関係の解決
パッケージマネージャーを使ってアプリケーションを開発するとき、通常何らかの設定ファイルを開発プロジェクト内に作成しそこに使用したいパッケージを記載します(図1)。
図1 開発プロジェクトの設定ファイル
このファイルで明示的に記載したOSSパッケージは言ってみれば「直接使っているOSS」です。次に、この設定ファイルを使ってアプリケーションの構築を行います。このときのパッケージマネージャーの動作は以下のようになります。
-
パッケージマネージャーが設定ファイルを読み込み、必要なパッケージを調べます。
-
必要なパッケージが未取得であればインターネット上の公開リポジトリを参照して取得します。
-
取得したパッケージにも設定ファイルがありこれをパッケージマネージャーが参照します。
-
取得したパッケージの設定ファイルに記載されている必要なパッケージをリポジトリから取得します。
-
さらに取得したパッケージの設定ファイルを参照して…と必要なすべてのパッケージが揃うまで2.から4.を繰り返します。
-
最終的に必要なパッケージがすべて開発環境側に取得され、開発に利用できるようになり、また、ランタイムライブラリとしてアプリケーションに組み込まれることになります。
この動きを図2に示します。
図2 構築時におけるパッケージマネージャーの動作
このようにパッケージマネージャーの依存解決によって、使用するパッケージの前提となっているパッケージも自動的に取得されます。これが「直接使っていないOSS」もアプリケーションに組み込まれるのです。
npmでの例
実際にnpmを使ったアプリケーションを例にして見てみましょう。npmはNode.js用に作られたパッケージマネージャーです。
設定ファイルはpackage.jsonです。この中に依存関係は以下のような設定をしました。このアプリケーションはVue.jsというWebUIフレームワークを使用しています。
dependencies": {
"core-js": "^3.26.0",
"vue": "^3.2.44"
},
devDependencies": {
"@babel/core": "^7.20.2",
"@babel/eslint-parser": "^7.19.1",
"@vue/cli-plugin-babel": "^5.0.8",
"@vue/cli-plugin-eslint": "^5.0.8",
"@vue/cli-service": "^5.0.8",
"eslint": "^7.32.0",
"eslint-plugin-vue": "^8.0.3"
},
図3 package.json(依存パッケージ部分抜粋)
依存するパッケージとしてdependenciesという項目にcore-jsとvueという2つのパッケージを指定しています。また、開発用のツールとしてdevDependenciesという項目に7つのパッケージを指定しています。
この環境でCLIからnpmのビルド/インストールを行うinstallコマンドを実行すると図5のように「957パッケージが追加された」と表示されました。
$ npm install
:
[ .................] / fetchMetadata: sill pacote range manifest for clone-deep@^4.0.1 fetched in 348ms :
(中略)
:
added 957 packages from 503 contributors and audited 958 packages in 186.723s
102 packages are looking for funding
run `npm fund` for details
found 0 vulnerabilities
図4 npm install実行結果(抜粋)
実際にパッケージがダウンロード/インストールされるnode_modulesフォルダを参照すると、大量のサブフォルダとファイルが作成されていることがわかります(図5)。
図5 node_modulesフォルダ
自動的にインストールされたパッケージの一覧はnpm listコマンドで確認できます。図6のように実行すると依存関係もわかるようにツリー形式で表示されます。
$ npm list
myapps@0.1.0 /home/user/vueapps/myapps
├─┬ @babel/core@7.21.0
│ ├─┬ @ampproject/remapping@2.2.0
│ │ ├─┬ @jridgewell/gen-mapping@0.1.1
│ │ │ ├── @jridgewell/set-array@1.1.2
│ │ │ └── @jridgewell/sourcemap-codec@1.4.14
│ │ └─┬ @jridgewell/trace-mapping@0.3.17
│ │ ├── @jridgewell/resolve-uri@3.1.0
│ │ └── @jridgewell/sourcemap-codec@1.4.14 deduped
│ ├─┬ @babel/code-frame@7.18.6
│ │ └─┬ @babel/highlight@7.18.6
│ │ ├── @babel/helper-validator-identifier@7.19.1 deduped
│ │ ├── chalk@2.4.2 deduped
│ │ └── js-tokens@4.0.0
│ ├─┬ @babel/generator@7.21.1
(以下略)
図6 npm list結果(抜粋)
また、License Checkerというプログラムを使用すると、組み込まれた各パッケージの名称/バージョンとともに開発元とライセンスも確認することができます(図7)。
$ license-checker
├─ @achrinza/node-ipc@9.2.6
│ ├─ licenses: MIT
│ ├─ repository: https://github.com/achrinza/node-ipc
│ ├─ publisher: B**** **** *****
│ ├─ path: /home/user/vueapps/myapps/node_modules/@achrinza/node-ipc
│ └─ licenseFile: /home/user/vueapps/myapps/node_modules/@achrinza/node-ipc/licence
├─ @ampproject/remapping@2.2.0
│ ├─ licenses: Apache-2.0
│ ├─ repository: https://github.com/ampproject/remapping
│ ├─ publisher: J**** R*****
│ ├─ email: ******@****
│ ├─ path: /home/user/@ampproject/remapping
│ └─ licenseFile: /home/usr/vueapps/myapps/node_modules/@ampproject/remapping/LICENSE
├─ @babel/code-frame@7.12.11
│ ├─ licenses: MIT
│ ├─ repository: https://github.com/babel/babel
│ ├─ publisher: S**** ****
│ ├─ email: ******@****
(以下略)
図7 License Checker実行結果(抜粋)
この結果を見ると、パッケージによってライセンス(licenses)も提供元(publisher)もまちまちとなっていることがわかります。
自動取得されたパッケージは直接指定したパッケージ(例ではVue.jsなど)の一部分あるいは専用のサブモジュールというわけではなく、それぞれが別個のOSSとして開発/提供されている物であることがわかると思います。
まとめ
パッケージマネージャーによる依存関係の解決で実際に行われることを説明してきました。まとめると以下のようになります。
- 各パッケージは前提パッケージに関する依存情報を持っている。
- パッケージマネージャーは依存情報を辿って必要なパッケージを自動的に取得しアプリケーションに組み込む。これが「直接使っていないOSS」と言われる理由である。
- 依存しているパッケージはそれぞれ別個に開発・配布されている異なるOSSである。
これらから依存パッケージは「直接使っていないOSS」であっても「間接的に使っているOSS」であり、アプリケーションに組み込まれるため正しく認識し管理する必要がわかると思います。
現代的な開発ではもはや必需と言えるパッケージマネージャーですが、決まった手順として使うだけではなく仕組みも理解して正しいOSS管理を心かけましょう。