もんりぃ is undefined.

育児ネタとか、技術ネタとか。

真・Unity Package Manager で Private な Scoped Registry を登録する方法

更新履歴

  • 2020/03/09 (Mon) 10:00 Unity 2019.3.4f1 に於ける認証対応について追記
  • 2019/07/24 (Wed) 15:00 中間CA証明書に関する記述を追加
  • 2019/07/18 (Thu) 22:00 プロキシサーバのホスト名や SSL 証明書などについて記載

この記事の内容は不要になりました

monry.hatenablog.com

というコトで、嬉しい民主化パンチのお陰で、本記事の対応は完全に不要になりました。やったね!

「昔はこういうコトをしなければいけなかったんじゃ…。」という昔話として、記事は残しておきます。

はじめに

この記事 で「Charles を使って Private Scoped Registry へのアクセスをプロキシする」と言ったな。

あれは嘘だ。

いや、嘘じゃないんだけど、セキュリティ的に微妙なのと、環境変数汚すことになるので、正直採択しない方が良いです。

もっとマトモな方法を思いついたので、お詫びして訂正いたします。

TL; DR

この記事 を読みましょう。
ローカルに nginx とかで Authorization ヘッダを付加するように設定したプロキシサーバを建てて、Unity Package Manager からはソレを参照させる形にすれば OK です。

前の記事の問題点

事実上の中間者攻撃とも言えるやり方

自分で分かってやっているとは言え、証明書を偽装してアクセスしているコトになるので、褒められたやり方とは言えません。*1

良い子のみんなは真似しちゃダメだぞ?

環境変数を汚す

プロキシの設定をグローバルな環境変数として定義してしまっています。

しかも HTTP_PROXY やら HTTPS_PROXY やらと他のアプリケーションも参照しそうなキーで設定しているので、後々ドハマリすること請け合いです。

良い子のみんなは真似しちゃダメだぞ?

解決案

Unity Package Manager が参照するサーバをプロキシサーバにする

Charles を用いた場合でも、通信をプロキシしているという意味では同じなのですが、仲介の順番を逆にします。

そもそも、「全ての通信をプロキシして、特定のパターンのみ改竄する」などというまどろっこしいコトをせずとも、「Unity Package Manager がアクセスする先をプロキシサーバそのものにする」コトで、やりたいことを実現できます。(何故気付かなかったし…orz)

限りなく閉じた環境にプロキシサーバを建てる

まぁ、つまり Docker 使ってプロキシサーバを建てれば、環境変数を汚す心配もないよね。って話です。

Unity Package Manager は URL ベースのリクエストを行うので、適当なホスト名を hosts とかで名前解決させつつ、そのホスト名で待ち受けている Docker コンテナ内のプロキシサーバに向ければ、世はこともなし。という寸法です。

いやいや、認証はどうなったのよ?

元々の問題点の根っこは Authorization ヘッダを付けられない所にありました。

これは、プロキシサーバがリクエストの仲介を行う際にリクエストヘッダを追加するコトで解消できます。

プロキシサーバ自体は、リクエストヘッダを追加する機能を持ったモノであれば何でも良いのですが、nginx がお手軽でしょう。

解決策

github.com

上記のリポジトリを clone して、 .env.sample を参考に .env ファイルを作って、 docker-compose up して立ち上がる https://my-registry.local を Scoped Registry の向き先とすれば OK です。

例えば my-registry.local というホスト名で my-registry.com 向けのプロキシサーバを立てる場合の .env は以下のようになります。

HOSTNAME=my-registry.local
PORT=443
AUTH_TOKEN=<base64 encoded authorization-token>
REGISTRY_URL=https://my-registry.com

詳細に解説すると以下のような感じになります。

Docker

Dockerfile

docker-compose.yaml

nginx をベースに、プロキシするための設定ファイルと自己署名証明書をコンテナ内の /etc/nginx/conf.d/ にコピーしています。

デフォルトの設定により /etc/nginx/conf.d/*.conf を読み込んでくれるので、これだけで設定は完了です。

SSL Certificates については mkcert コマンドで作成することを前提にしていますが、自己認証局に登録された自己署名証明書を作れれば何でも良いです。
後述する

なお、 envsubst によりテンプレートとなる設定ファイル内の一部文字列を .env に定義した環境変数に置換してから nginx を起動しています。

また、 restart: always で Docker のデーモン起動時(≒OS 起動時)に自動的に起動するように設定しています。

nginx

upm-proxy.conf

上述の nginx に食わせるプロキシ用の設定です。

ポイントは、 proxy_set_headerAuthorization ヘッダを上書きしている所にあります。

Private Scoped Registry により発行された認証トークンを .env に定義し、nginx 起動時に設定ファイルに流し込むことで、無事にプロキシ時に Authorization ヘッダが追加される運びとなります。*2

なお、 Verdaccio の仕様上 Host, X-Forwarded-For, X-Forwarded-Proto あたりのヘッダも必要になるので、それも設定しています。

Unity Package Manager

/Library/Application Support/Unity/config/upm-config.json

Unity Package Manager が npm を介して SSL 通信を行う際の中間認証局証明書を定義します。
これが無いと、 self signed certificate in certificate chain とかって怒られます。

オレオレ証明書に署名した際の自己認証局の中間 CA 証明書を /Library/Application Support/Unity/config/SelfSigned.pem に置いて、そのパスを記載します。

{
  "caFile": "/Library/Application Support/Unity/config/SelfSigned.pem"
}

Packages/manifest.json

{
  "scopedRegistries": [
    {
      "name": "Proxy Unity Package Manager Registry",
      "url": "http://localhost:4873",
      "scopes": [
        "dev.sample.upm"
      ]
    }
  ],
  "dependencies": {
    "dev.sample.upm.some-package": "1.0.0",
    // ...
  }
}

Packages/manifest.jsonscopedRegistries に設定する URL を http://localhost:4873 に変更します。

Verdaccio

このままだと、Verdaccio の API が返す URL はプロキシサーバのソレではなく、Registry の URL のままとなってしまいます。

そうなると、最も肝心な Package を取得する際のリクエストに Authorization ヘッダをつけることができません。

Verdaccio では、この問題のワークアラウンドとして返却する URL の一部を特定のリクエストヘッダで置換する機能があります。

具体的には以下のリクエストヘッダを送信しておけば、期待する URL を構築してくれるので、 nginx の設定に追加しましょう。

Header Value
Host 返却を期待するホスト名
my-package.local など
nginx の場合 $host を入れておくと良い
X-Forwarded-For アクセス元 IP アドレス
ホスト側が LoadBalancer 通すような設定の場合コレ設定しておいた方が良い
X-Forwarded-Proto 返却を期待するプロトコル
GCP Ingress の場合渡してくれないっぽいけど、一応設定しておく

おわりに

月曜の開発チーム定例で「こんな無理矢理なやり方(前記事のようなやり方)であれば出来たんだけど、ダメだよねぇ…。」みたいな感じになって「んー、諦めるか。」となってたんですが、その日の夕方くらいにアイディアが降ってきて、夢中で設定してました。

上記のプロキシサーバ の設定として、複数のレジストリに接続できるようにパスに応じて Auth Token 切り替えるとかも対応できるっちゃできるんですが、面倒なので流石にそこまではやっていません。
~/.npmrc を読んでトークン渡すようなスクリプト書くと良いんじゃないでしょうか。PR お待ちしています。)

*1:今回紹介するやり方も、本質的には同じコトなんだけど、ホスト名絞ってたりローカルの Docker でゴニョゴニョする感じだったりするので、まぁ許してください。

*2:なお、npm の仕様的に Bearer として渡す必要があるようです。