Swift Digest
SE-0291 | Swift Evolution

Package Collections

Proposal
SE-0291
Authors
Boris Bügling, Yim Lee, Tom Doron
Review Manager
Tom Doron
Status
Implemented (Swift 5.5)

01 何が問題だったのか

Swift のパッケージエコシステムが広がるにつれて、「目的に合ったパッケージを見つけ出す」ことが次第に難しくなってきました。SwiftPM 自体には、ある用途に向いたパッケージを探したり、公開された情報以上のメタデータ(著者、対応プラットフォーム、ライセンス、既知の CVE など)を確認したりする仕組みがなく、利用者は Web 検索や口コミに頼って探すしかありません。

個別の利用者にとっての問題

初心者やあるドメインに不慣れな開発者にとっては、そもそもどのパッケージが存在するのかを知ること自体が大きな負担です。さらに見つけたパッケージが信頼できるものなのか、自分の用途に適しているのか、プロジェクトに組み込む前に判断する材料も乏しい状態でした。パッケージの Package.swift に書かれていないメタデータ(たとえば利用可能なバージョン一覧、対応プラットフォーム、対応 Swift バージョン、CVE 情報など)を一元的に取得する標準的な手段がないためです。

キュレーションを共有する手段がない

一方で、教育者や企業、コミュニティの影響力ある人々には、「この用途にはこのパッケージ群を使うとよい」という知見が溜まっています。たとえば授業で扱うパッケージ一式や、社内で検証済みの推奨パッケージ群です。しかしそれを受講者や社内メンバーと共有する標準的な仕組みがないため、毎回口頭やドキュメントで案内するしかなく、初回利用時の摩擦や「どれを選べばよいか」という認知的な負荷が繰り返し発生していました。

Package Index や Package Registry では埋まらない隙間

パッケージ発見の領域には、役割の異なる3つの構成要素が想定されています。

  • Package Registry: Git から直接取得するのに代わり、パッケージソースをホスティング・配信する仕組み。不変性や耐久性の向上が目的で、別 Proposal で扱われます。
  • Package Index: 任意の場所にホストされたパッケージを横断的に検索可能にする、メタデータ豊富な検索インデックス(例: Swift Package Index)。インフラを伴う大規模な仕組みです。
  • Package Collections: より軽量な「キュレーションリスト」。個人や組織が手軽に作って共有できる単位でのパッケージ集合。

このうち、Package Index のように大規模なインフラを必要とせず、誰でも手軽に「推奨パッケージ集」を作って共有できる仕組みが欠けていました。SwiftPM 自身がパッケージ発見について何も知らないため、IDE やその他のツールがこうした情報を横断的に活用することもできません。

02 どのように解決されるのか

SwiftPM に Package Collections(パッケージコレクション)という新しい概念と、それを扱うための swift package-collection コマンド群が導入されます。Package Collection は、パッケージの一覧と各パッケージのメタデータを含む静的な JSON ドキュメントで、Web サーバや CDN に公開することで誰でも購読できるようになります。

たとえば授業を担当する講師が、課題で使うパッケージをまとめた JSON を GitHub リポジトリや自分のサイトに置き、その URL を学生に配布します。学生は SwiftPM にそのコレクションを追加するだけで、講師が選んだパッケージを検索・参照できるようになります。

コレクションの内容は libSwiftPM 経由でも取得できるため、他のツール(IDE など)が同じ情報を活用した豊かなパッケージ発見体験を提供することも可能になります。

コレクションの管理

ユーザーはコレクションを URL で追加・削除・一覧でき、追加済みのコレクションはローカルにキャッシュされたうえで検索の対象になります。

$ swift package-collection add https://www.example.com/packages.json
Added "My organisation's packages" to your package collections.

$ swift package-collection list
My organisation's packages - https://example.com/packages.json
...

$ swift package-collection remove https://www.example.com/packages.json
Removed "My organisation's packages" from your package collections.

add には --order N で検索結果のランキングに使うヒントを与えることもでき、これは SwiftPM のクライアント(UI を持つツールなど)が結果の並び替えに利用できます。

キャッシュの手動更新は refresh で行えます。SwiftPM は各種タイミングで自動更新も行いますが、検索などはローカルキャッシュに依存するため、必要に応じて明示的にリフレッシュできるようになっています。

$ swift package-collection refresh
Refreshed 23 configured package collections.

パッケージの検索とメタデータ取得

キーワード検索とモジュール名検索の 2 種類が提供されます。キーワード検索は、パッケージの名前や説明といったメタデータに対する文字列検索です。

$ swift package-collection search --keywords yaml
https://github.com/jpsim/yams: A sweet and swifty YAML parser built on LibYAML.
...

モジュール名検索は、指定したモジュール名を含むパッケージを最新バージョン基準で探します。結果が少数に絞れる前提で、1件あたりより多くのメタデータが表示されます。

$ swift package-collection search --module yams
Package Name: Yams
Latest Version: 4.0.0
Description: A sweet and swifty YAML parser built on LibYAML.
...

見つけたパッケージについては describe でメタデータを確認できます。パッケージ全体の情報に加え、最新バージョンの詳細(対応プラットフォーム、対応 Swift バージョン、モジュール一覧、ライセンス、CVE など)が得られます。

$ swift package-collection describe https://github.com/jpsim/yams
Description: A sweet and swifty YAML parser built on LibYAML.
Available Versions: 4.0.0, 3.0.0, ...
Watchers: 14
Readme: https://github.com/jpsim/Yams/blob/master/README.md
Authors: @norio-nomura, @jpsim
--------------------------------------------------------------
Latest Version: 4.0.0
Package Name: Yams
Modules: Yams, CYaml
Supported Platforms: iOS, macOS, Linux, tvOS, watchOS
Supported Swift Versions: 5.3, 5.2, 5.1, 5.0
License: MIT
CVEs: ...

--version を指定すれば特定バージョンのメタデータのみを取得でき、--json を付ければ機械可読な JSON で出力されます。なお、コレクションに保存されるのはメジャー/マイナーの組み合わせごとに最新パッチバージョンだけ、という制約があります。

describe は引数の URL がパッケージなのかコレクションなのかを自動で判別します(まずパッケージとして照合し、該当が無ければコレクションとして扱う)。--version を付けた場合は明示的にパッケージを指定していると判断され、コレクションへのフォールバックは行われません。

設定ファイルの場所

ユーザーごとの SwiftPM 設定ファイルが ~/.swiftpm/config(プラットフォーム相当の場所)に置かれるようになり、初期の用途としてコレクション一覧がここに保存されます。これは SwiftPM コマンド経由で管理される前提で、ユーザーが手で編集するものではありません。形式は実装詳細ですが、人間可読(実際には JSON)になる予定です。

補助的に、git の設定ファイルに似たキー・バリュー形式のファイルを置いて、メイン設定から参照することで、複数ユーザー/マシン間で設定を共有しつつユーザー固有の値を分離できる仕組みも用意されます。

データ形式

コレクションの JSON 形式は swift-package-collection-generator リポジトリ で定義されますが、当面は安定 API とは扱われません。そのため、ユーザーは自分で JSON を手書きせず、提供される生成ツールを使うことが推奨されます。将来的に形式が成熟した段階で、別 Proposal として安定化が検討される予定です。

コレクションの署名

コレクションは署名によって発行者の真正性と内容の完全性を保証できます。署名は任意で、署名されていないコレクションも追加可能です。

署名の生成には、対象の JSON、コード署名証明書、その秘密鍵、および証明書チェーン全体が必要です。生成された署名には公開鍵とチェーンが含まれ、後で検証に使われます。署名付きコレクションには次のような signature オブジェクトが追加されます。

{
  // Package collection JSON
  ...,
  "signature": {
    ...
  }
}

署名証明書の要件

署名生成・検証の両方で次の条件が確認されます。

  • 操作時刻が証明書の有効期間内であること。
  • 証明書の Extended Key Usage に Code Signing が含まれていること。
  • 鍵は 256-bit EC(推奨)か 2048-bit RSA であること。
  • 証明書が失効していないこと(OCSP 対応、AIA 拡張に OCSP レスポンダの URL が含まれる必要あり)。
  • 証明書チェーンが有効で、ルート証明書が信頼されていること。

Apple プラットフォームでは OS に同梱されたルート証明書が自動的に信頼されます。追加の証明書を信頼したい場合や、非 Apple プラットフォーム(デフォルトでは信頼されたルートが存在しない)で検証を有効にしたい場合は、~/.swiftpm/config/trust-root-certs ディレクトリに証明書を配置します。

署名付きコレクションの追加

署名付きコレクションを add すると、SwiftPM は「署名対象(signature 以外の JSON 本体)が改変されていないか」と「証明書が上記要件を満たすか」をチェックし、いずれかに失敗するとインポートを拒否します。

検証をスキップしたい場合は --skip-signature-check を付けます。

$ swift package-collection add https://www.example.com/packages.json --skip-signature-check

非 Apple プラットフォームではデフォルトでは署名検証が必ず失敗するため、SwiftPM は trust-root-certs を設定するか --skip-signature-check を使うかを案内します。

署名なしコレクションの追加

署名が無いコレクションについては、ユーザーが明示的に信頼を表明するために --trust-unsigned が必要になります。--skip-signature-check は署名なしコレクションには効果がありません。

$ swift package-collection add https://www.example.com/unsigned-packages.json --trust-unsigned

想定される攻撃と対策

現在の設計では次の攻撃が想定されます。

  • 署名の剥ぎ取り: 攻撃者が署名を削除して「署名なし」として通そうとする。発行者は署名付きであることを周知し、ユーザーは署名付きと聞いているコレクションで「署名なし」の警告が出た場合は add を中止するべきです。
  • 署名の差し替え: 攻撃者がコレクションを改変し、自分の証明書で署名し直す。SwiftPM は署名が正当でさえあれば受け入れてしまいます。

これらの対策として、SwiftPM は発行者が次の二つを宣言できる仕組みを提供します。

  1. 自分のコレクションに対しては必ず署名検証を要求する(剥ぎ取り対策)。
  2. 署名に使える証明書を限定する(差し替え対策)。

これらは SwiftPM の証明書ピンニング設定へ pull request を送る形で登録します。

今後の見通し

このプロポーザルが提示しているメタデータ項目は初期セットであり、今後の運用を通じて拡張されていく余地が残されています。~/.swiftpm/config の仕組みも、将来のユーザーごとの設定を置くための基盤として使えます。また、Package Collections はキュレーションされたパッケージ集合を SwiftPM が理解するための最初の一歩であり、将来的にはよりダイナミックなサーバーベースのインデックスを同じ設計の上に構築していく、という見通しが示されています(いずれも現時点では speculative な方向性です)。