tenant-pass

ゲートウェイ連携(独自のゲートウェイ)

自前のゲートウェイやプロキシでパスに署名するときに守る決まりを説明します。すべきこととしてはいけないこと、鍵の入れ替え、実装の確かめ方、forward-auth での実装例を扱います。

このページの見出し
  1. 基本の決まり
  2. 署名するときに守ること
  3. 鍵の置き場所
  4. 署名してはいけない場面
  5. バックグラウンドジョブ
  6. 鍵の入れ替え
  7. 実装の確かめ方
  8. forward-auth での実装例

まず、自分で作る必要があるかを確かめてください。tenant-pass には、このページの決まりをすべて守ったゲートウェイ tenant-pass-gateway が付いています。このページは、それを使わずに、既存のプロキシや自前の部品でパスを発行したい人のためのものです。

ゲートウェイは、どのリクエストにパスを渡すかを決める部品です。tenant-pass の安全性は、この判断が正しいかどうかにかかっています。作り始める前に、脅威モデル の該当する節に目を通してください。

基本の決まり

テナント T とアプリケーション A のパスに署名してよいのは、次の 2 つを両方満たす場合 だけ です。1 つは、そのリクエストがテナント T のものとして認められていることを、ゲートウェイ自身が確かめたこと(T に属するユーザーの認証済みセッションがある、または T のための署名付きのジョブの許可がある)。もう 1 つは、ゲートウェイがそのリクエストを A に転送することです。

パスは、転送先を決めるための目印ではありません。「いま、テナント T の認証情報を取得してよい」という権限そのものです。

署名するときに守ること

署名の前に、エンドユーザーのセッション(Cookie、OIDC のトークン、mTLS)を認証し、そのセッションが要求されたホストのテナントのものかを確かめます。テナント B の有効なセッションから、テナント A のホストのパスを作ってはいけません。

ホスト名は正規化してから使います。小文字にし、ポートと末尾のドットを除き、国際化ドメイン名は xn-- で始まる ASCII の形にします。Host ヘッダーを、正規化もテナントのドメインの一覧との照合もせずにコピーするのは誤りです。

aud には、実際にリクエストを受け取るアプリケーション(転送先のアップストリーム)を入れます。リクエストに含まれている値を使ってはいけません。

有効期間は短くします。リクエストを処理するだけなら exp = iat + 30s で足ります。

jti は暗号論的に安全な乱数(16 バイトを base64url にしたもの)で作り、ゲートウェイのログにも残しておきます。tenant-pass の監査ログと突き合わせられるようになります。

パスは、クライアントが自分で付けられないリクエストヘッダー(たとえば Tenant-Pass)に入れてアプリケーションに渡します。そのためには、入口のプロキシで、クライアントから届いた同じ名前のヘッダーを必ず取り除いておきます。

鍵の置き場所

秘密鍵はゲートウェイにだけ置きます。ゲートウェイのプロセスだけが読めるファイルか、HSM や KMS に保管してください。ゲートウェイのデプロイごと(環境やクラスターごと)に kid を分けて別の鍵を使うと、問題が起きた鍵だけを無効にできるのでおすすめです。

ゲートウェイの鍵をほかの用途に使い回すのもやめてください。PASETO の暗黙のアサーションがあるので、別の用途のトークンと取り違えられることはありません。それでも鍵を分けておけば、安全性を考えるのがずっと楽になります。

署名してはいけない場面

次の URL には、認証されたセッションがないので、パスを付けてはいけません。

  • ログイン、サインアップ、パスワードのリセット
  • ヘルスチェック、静的なファイル、公開 API
  • 外部のサービスからの Webhook

テナントのディレクトリでまだ確認が済んでいないホスト名(持ち主の確認が終わっていない独自ドメインなど)にも署名しないでください。

見落としやすいのが、内部のネットワークからの呼び出しです。好きな Host ヘッダーを付けて、ゲートウェイ経由で自分自身にリクエストを送れるアプリケーションは、どのテナントのパスでも手に入れられてしまいます。ネットワークの設定で遮断するか、外部向けのリスナーに届いた通信にだけ署名するようにしてください。

バックグラウンドジョブ

キュー、cron、マイグレーションのようにエンドユーザーから始まらない処理には、ゲートウェイが確かめられるセッションがありません。こうした処理のためにパスの有効期間を延ばすのはやめて、ジョブチケット を使ってください。

鍵の入れ替え

  1. 新しい鍵を作ります。tenant-pass gen-gateway-key -private gw-2026-12.key -public gw-2026-12.pub
  2. tenant-pass に新しい鍵を覚えさせます。すべてのレプリカの gateway_keys に新しい公開鍵を新しい id で追加し、設定を反映します。先にこちらをやっておかないと、切り替えた直後のパスが拒否されます。
  3. ゲートウェイを、新しい鍵と kid で署名するように切り替えます。
  4. 古い鍵で署名したパスが残らず期限切れになるまで待ちます。pass.max_ttl が過ぎたら、古い鍵を gateway_keys から消して、もう一度設定を反映します。

実装の確かめ方

署名の実装は、テストベクター と 1 バイトも違わない結果を出さなければなりません。確かめ方は 2 つあります。動いている tenant-pass に curl でパスを送ってみる方法(はじめに の手順 5 と同じ)と、次のコマンドで作った基準のパスと手元でデコードして比べる方法です。

tenant-pass sign-pass -key gw.key -kid gw1 -aud billing -host acme.example.com   # 比べるための基準の出力

forward-auth での実装例

Caddy の forward_auth、nginx の auth_request、Traefik の ForwardAuth、Envoy の ext_authz のように、多くのプロキシはリクエストを転送する前に別のサービスへ許可を問い合わせられます(forward-auth)。これを使うなら、問い合わせを受ける小さなサービスを作り、次の処理をさせます。

  1. 元のリクエストのヘッダーを受け取ります。
  2. セッションを検証し、どのテナントのものかを調べます。
  3. 要求されたホストが、そのテナントのものであることを確認します。
  4. レスポンスヘッダー Tenant-Pass: <pass> を付けて 200 を返します。プロキシは、このヘッダーをアップストリームへのリクエストにコピーします。
  5. 条件を満たさないときは 401 か 403 を返します(パスは決して返しません)。

このサービスが tenant-pass から借りるのは、Go の pass パッケージ(pass.Signer)だけです。

この文書は、リポジトリにある docs/ja/gateway-integration.md をもとにしています(英語版は docs/en/gateway-integration.md)。