Swift Digest
SE-0378 | Swift Evolution

Package Registry Authentication

Proposal
SE-0378
Authors
Yim Lee
Review Manager
Tom Doron
Status
Implemented (Swift 5.8)

01 何が問題だったのか

パッケージレジストリによっては、一部または全部のAPIを利用するために認証を要求したい場合があります。認証によって、操作を行うユーザを識別し、そのリクエストを適切に認可できるようになります。

しかし、SwiftPMがこれまでサポートしていたのはBasic認証(ユーザ名とパスワード)のみで、多くのWebサービスで一般的なアクセストークンやOAuthといった方式には対応していませんでした。これでは、近年のパッケージレジストリサービスと連携するにあたって認証方式の選択肢が不足しています。

加えて、SE-0292 で用意されていた認証情報の登録手段は swift package-registry set サブコマンドに --login--password を渡すというもので、認証情報が正しいかを事前に検証する仕組みもなく、格納先もプロジェクトレベルのnetrcファイルを含む複数の場所に分散しており、意図せず認証情報をコミットしてしまうリスクもありました。認証方式の拡張と、より安全で一貫した認証情報の管理方法の両方が求められていました。

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

swift package-registry コマンドに、docker loginnpm login からの着想を得た login / logout サブコマンドを追加し、Basic認証に加えてトークン認証をサポートします。将来の認証方式の追加にも対応できる設計になっています。認証情報は可能な限りOSのネイティブな認証情報ストア(macOSではKeychain)に保存し、それが利用できない環境でのみユーザレベルのnetrcファイルにフォールバックします。

login サブコマンド

指定したレジストリに対して認証情報を登録します。SwiftPMはレジストリ側の login APIに対して認証情報を検証するリクエストを送り、成功したら認証情報を永続化します。

swift package-registry login <url> [options]

OPTIONS:
  --username     ユーザ名
  --password     パスワード
  --token        アクセストークン
  --no-confirm   netrcファイルへの書き出しを確認なしで許可
  --netrc-file   netrcファイルのパスを指定
  --netrc        OS側の認証情報ストアが使える場合でもnetrcファイルを使う

<url> はレジストリのベースURL(例: https://example-registry.com)を指定します。HTTPSが必須です。login APIのパスがデフォルトの /login ではない場合は、APIのフルURL(例: https://example-registry.com/api/v1/login)を渡します。

オプションの組み合わせから認証方式が決まります。

認証方式 必要なオプション
Basic --username, --password
Token --token

対話モードでは、不足している --password--token の入力を促されます。たとえば --username のみが指定されていればBasic認証とみなしてパスワードを入力するプロンプトを出します。非対話モードで使う場合は、必要な値を明示的に渡すか、事前に認証情報ストアに登録しておきます。

例: Basic認証(macOS、対話モード)

> swift package-registry login https://example-registry.com \
    --username jappleseed
Enter password for 'jappleseed':

Login successful.
Credentials have been saved to the operating system's secure credential store.

この例では example-registry.com に対するエントリがKeychainに追加されます。

例: トークン認証

> swift package-registry login https://example-registry.com \
    --token jappleseedstoken

トークン認証の場合、ログイン名を token、パスワードをアクセストークン本体として認証情報ストア(またはnetrcファイル)に保存されます。

OS側の認証情報ストアが使えない環境では、netrcファイルへ書き出してよいかを確認するプロンプトが出ます。--no-confirm を付けるとこの確認を省略できます。--netrc を付けると、OS側のストアが使える環境でも強制的にnetrcファイルを使わせることができます。

logout サブコマンド

レジストリからログアウトします。OS側の認証情報ストアとユーザレベルの registries.json からエントリを取り除きます。機密情報を誤って消さないよう、netrcファイルは自動更新せず、ユーザ自身で編集する必要があります。

swift package-registry logout <url>

registries.json の拡張

ユーザレベルの ~/.swiftpm/configuration/registries.json に新しく authentication キーを追加し、どのレジストリがどの認証方式を必要とするかを記述します。

{
  "registries": {
    "[default]": {
      "url": "https://example-registry.com"
    }
  },
  "authentication": {
    "example-registry.com": {
      "type": "basic",
      "loginAPIPath": "/api/v1/login"
    }
  },
  "version": 1
}

typebasic または token のいずれかです。loginAPIPath は任意で、デフォルトの /login を上書きしたいときに指定します。login サブコマンドは成功時にこのファイルを自動で更新します。

認証情報の保存先

認証情報は、プラットフォームごとに最も安全な方法で扱われます。

  • Basic認証・macOS Keychain: “Internet password” として保存し、Keychainの “item name” は https://example-registry.com のようにスキーム付きのレジストリURLになります。
  • Basic認証・netrcファイル(OS側のストアが使えない場合のフォールバック):

    machine example-registry.com
    login jappleseed
    password alpine
    
  • トークン認証・netrcファイル: ログイン名を token 固定にして、トークン本体をパスワード欄に書きます。

    machine example-registry.com
    login token
    password jappleseedstoken
    

SwiftPMはデフォルトではユーザのホームディレクトリのnetrcファイルを参照し、--netrc-file で別のパスを指定できます。初期リリースでKeychainが使えるのはmacOSのみで、他のプラットフォームではユーザレベルのnetrcファイルにフォールバックします。

SwiftPM側の動作変更

  • プロジェクトレベルのnetrcファイルは非対応になります。 netrcファイルの誤コミットによる情報漏えいを防ぐため、ユーザレベルのnetrcファイルのみを参照します。
  • 認証情報の探索先はひとつだけになります。 macOSではKeychain、それ以外のプラットフォームではユーザレベルのnetrcファイルです。
  • --disable-keychain--disable-netrc オプションは削除されます。

レジストリサービス側のAPI

認証が必要なレジストリは、login APIを実装する必要があります。SwiftPMは認証情報を検証するためにこのAPIへHTTP POST リクエストを送り、その際に次のような Authorization ヘッダを付けます。

  • Basic認証: Authorization: Basic <base64 encoded username:password>
  • トークン認証: Authorization: Bearer <token>

レジストリ側は認証成功時に 200、失敗時に 401 を返します。対応していない認証方式が指定された場合は 501 を返します。SwiftPMは 200 が返ってきたときだけ認証情報をローカルに永続化します。