Swift Digest
SE-0321 | Swift Evolution

Package Registry Service - Publish Endpoint

Proposal
SE-0321
Authors
Whitney Imura, Mattt Zmuda
Review Manager
Tom Doron
Status
Accepted (2021-09-01)

01 何が問題だったのか

SE-0292 で Package Registry サービスの仕様が導入され、パッケージを取得するためのエンドポイントが定義されました。しかし、SE-0292 の範囲では「新しいリリースをレジストリに公開(publish)する」手段については標準化されていませんでした。

そのため、あるリリースをユーザーが取得できるようにするかどうかは、各レジストリの帯域外(out-of-band)の仕組みに委ねられていました。たとえば公開 Swift パッケージのインデックスを参照し、有効なバージョン番号のタグが付いているコミットを自動的にリリースとして取り込む、といった実装が個別に行われます。

この状態では、

  • メンテナが自分のパッケージを能動的に配布する標準的な方法が無い
  • レジストリサービス提供者ごとにパブリッシュ手順が異なり、ツールや CI/CD との連携が作りづらい
  • 結果としてレジストリ間の相互運用性も損なわれる

といった問題がありました。パッケージを公開するためのエンドポイントを仕様の一部として定めることで、メンテナに配布手段を与えつつ、レジストリ実装同士の互換性を高めることが求められていました。

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

Package Registry の仕様に、パッケージリリースを公開するためのエンドポイントを追加します。対象は Swift Package Manager 本体ではなく、あくまでレジストリサービスが実装する Web API の仕様です。

追加されるエンドポイント

次の 1 本のエンドポイントが追加されます。

メソッド パス 説明
PUT /{scope}/{name}/{version} パッケージリリースを作成する

このエンドポイントの実装はレジストリにとって任意(optional)です。つまり、取得専用のレジストリとして運用することも引き続き可能です。また、一度公開されたリリースは原則として削除されない(durability)前提で設計されており、最終的に何を公開可能とするかはレジストリ側が判断します。

公開フローの例

メンテナが mona.LinkedList1.1.1 をリリースしてレジストリ packages.example.com に登録する場合、おおむね次の流れになります。

まず、swift package archive-source サブコマンドでパッケージのソースを Zip アーカイブ化します。

$ swift package archive-source

次に、生成された Zip をマルチパートで PUT リクエストにのせてレジストリへアップロードします。version はクエリで指定します。

$ curl -X PUT --netrc                                      \
       -H "Accept: application/vnd.swift.registry.v1+json" \
       -F source-archive="@LinkedList-1.1.1.zip"           \
       "https://registry.example.com/mona/LinkedList?version=1.1.1"

レジストリはこのリクエストを同期/非同期のどちらで処理することもできます。サーバ側で品質チェックや内部データストアの更新などを行う余地を残すためです。

受理・処理が終わると、当該バージョンが一覧エンドポイントから返るようになります。

$ curl -X GET -H "Accept: application/vnd.swift.registry.v1+json" \
       "https://registry.example.com/mona/LinkedList"             \
    | jq ".[] | keys"
[
  "1.0.0",
  "1.1.0",
  "1.1.1",
]

以降、依存解決を行うクライアントは 1.1.1 を検出し、必要に応じて GET /mona/LinkedList/1.1.1.zip でソースアーカイブを取得できます。

「push」と「pull」の統合

公開モデルには、成果物をクライアントからサーバへアップロードする push 型と、メンテナはレジストリに通知するだけでサーバ側がソースコードを取り寄せてパッケージ化する pull 型の考え方があります。本 proposal では検討の結果、両者を別エンドポイントに分けず、単一の PUT エンドポイントで両方を表現できる形にしました。pull 型は「クライアントとサーバが同一主体である push」とみなせるためです。

セキュリティ上の考慮点

公開エンドポイントは攻撃対象となりやすいため、仕様として次のような注意点が示されています。実装する各レジストリが、これらに対応することが期待されています。

  • なりすまし対策として、公開リクエストに対する多要素認証の利用が推奨されます。また、既存のパッケージ名と似た名前での登録(typosquatting)を検出するため、Damerau–Levenshtein 距離などの文字列類似度で既存名と比較する運用も推奨されます。
  • アップロードされた Zip アーカイブの展開時には、Zip Slip や Zip bomb のような既知の脆弱性・弱点への対策が必要です。
  • リリース内容に関する紛争に備え、SBOM(software bill of materials)やデジタル署名といった、来歴(provenance)を裏付ける仕組みと組み合わせられる設計になっています。
  • アップロードされたアーカイブにクレデンシャルなどの機微情報が含まれていないか、サーバ側でスキャンして拒否することが推奨されます。
  • 大きなペイロードによる DoS を避けるため、リクエストボディを処理する前に認証を済ませる、最大サイズを制限する、リバースプロキシ/ロードバランサを経由させる、といった一般的な防御策が推奨されます。
  • Package.swift は実行可能なコードであり、プラットフォームや依存関係などのメタ情報を得るには Swift ツールチェーンで評価する必要があります。悪意ある Package.swift による遠隔コード実行を避けるため、マニフェストの評価は権限を絞ったコンテナ内で行うことが推奨されます。

既存パッケージへの影響

公開エンドポイントは、既存の URL ベースの配布から新しいレジストリ方式への移行経路を、各レジストリ運用者が用意するための土台になります。具体的な移行ポリシー(どのパッケージを取り込むか、どの時点から公開エンドポイント経由のみに切り替えるか等)は、各レジストリ運用者の裁量に委ねられます。

Future Directions(参考)

今後の発展として、次のようなアイデアが挙げられています。いずれも本 proposal のスコープ外で、実現が約束されているわけではありません。

  • swift package publish サブコマンドの追加。.swiftpm/config/registries.json からレジストリ設定を読んだり、.netrc で認証したり、swift package archive-source を内包して SBOM 生成や署名までまとめて行うクライアント側 UI が考えられます。CLI を肥大化させたくない、CI/CD 内で公開する利点を損ないたくないという懸念から、コア機能からは切り離されています。
  • 新しいリリースを Activity Streams や RSS で配信するシンジケーション機構。パッケージインデックスやレジストリ間フェデレーションの情報源になり得ます。
  • Trillian や sigstore のような append-only ログを使ったトランスペアレンシーログ。リリースごとに追記することで、公開履歴の検証を可能にする方向性です。