tenant-pass

バックグラウンドジョブ(ジョブチケット)

リクエストの後で動く処理に使うジョブチケットを説明します。パスとの違い、POST /v1/job-tickets による発行、スケジューラーなどの信頼された発行者、設定、チケットの形式、セキュリティ上の性質と制限を扱います。

このページの見出し
  1. チケットを手に入れる 2 つの方法
  2. 設定
  3. チケットの形式
  4. セキュリティ上の性質と制限

パス(pass)は 1 つのリクエストに結び付いていて、有効期間は数秒しかありません。エクスポートや夜間のレポートのように、リクエストが終わった後で動く処理には間に合いません。そうした処理に「このテナントのデータを扱ってよい」と許すための、パスとは別のトークンがジョブチケットです。

項目パスジョブチケット
署名するものゲートウェイtenant-pass 自身、または利用者が運用する信頼された発行者(スケジューラーなど)
書かれているものホスト名またはテナント必ずテナント ID
有効期間数秒(pass.max_ttl。5 分以下)数分から数時間(job_tickets.max_ttl。24 時間以下)
暗黙のアサーションtenant-pass/v1/passtenant-pass/v1/job
認証情報との交換POST /v1/credentials に {"pass": …} を送るPOST /v1/credentials に {"ticket": …} を送る
レート制限limits.max_tenants_per_minuteapps[].jobs.max_tenants_per_minute(パスとは別に数える)

暗黙のアサーション(署名には含めるが、トークンには入れない文字列)が違うので、仮に同じ鍵で両方に署名しても、パスをチケットとして使ったり、その逆をしたりはできません。

チケットを手に入れる 2 つの方法

1. リクエストの続きの処理: POST /v1/job-tickets

ユーザーの操作で始まる処理(エクスポート、インポート、頼まれたレポートの送信など)には、こちらを使います。アプリケーションは、新しいパスの付いたユーザーのリクエストを処理している間に、パスをチケットに替えてジョブのデータに入れておきます。ジョブは後で、チケットの期限が切れるまで何度でも、チケットを認証情報と交換できます。

  1. ユーザーのリクエストが、ゲートウェイを通ってパスと一緒にアプリケーションに届きます。
  2. アプリケーションは POST /v1/job-tickets にパスを送り、ticket、tenant、expires_at を受け取ります。
  3. アプリケーションは、テナント(例: acme)とチケットを入れたジョブをキューに登録します。
  4. 後で、ワーカーがチケットを POST /v1/credentials に送り、acme の認証情報を受け取ります。

この方法を使うには、job_tickets.signing_key(手元の鍵か Vault Transit の鍵)を設定し、アプリケーションの設定に "jobs": {"issue": true} を書きます。

発行の時点で、パスがそのアプリケーションに対して有効で、テナントが有効になっていて、そのアプリケーションに許可されている必要があります。tenant-pass はこの時点でテナントを確定させるので、チケットには正式なテナント ID が書かれます。

チケットの発行も、認証情報の取得と同じ「1 分間に扱うテナントの数」に数えます。チケットを経由してこの制限をすり抜けることはできません。

2. 定期的な処理: 信頼された発行者

夜間のレポート、後片付け、マイグレーション、課金の処理のように、どのリクエストからも始まらない処理には、パスを作れる人がいません。この場合は、利用者が運用している信頼できる部品(スケジューラーや、管理画面のジョブを動かす仕組みなど)が自分の鍵でチケットに署名します。tenant-pass の側では、その鍵を job_tickets.trusted_issuers に登録して信頼させます。

  1. スケジューラー(発行者の鍵を持っています)が、{aud: reports, tenant: acme, exp: +1h} のチケットに署名してワーカーに渡します。
  2. ワーカーがチケットを POST /v1/credentials に送り、acme の認証情報を受け取ります。

どのテナントの処理を動かすかは発行者が決めます。とはいえ tenant-pass も、交換のたびにテナントが存在し、有効で、そのアプリケーションに許可されているかを確かめ、ジョブ用のレート制限をかけます。

発行者の側でチケットに署名するには、Go なら pass.Signer{KeyID: "scheduler-1", Key: key}.SignJob(ctx, pass.JobClaims{...}) を使います(Vault Transit の鍵なら Remote: を指定)。コマンドラインからは次のようにします。

tenant-pass sign-job-ticket -key scheduler.key -kid scheduler-1 -aud reports -tenant acme -ttl 1h

ほかの言語でも、下の「チケットの形式」のとおりに作れば、どの PASETO v4.public のライブラリでも署名できます。

発行者は、必ずアプリケーションとは別の部品にしてください。アプリケーションがチケットに署名できたら、乗っ取られたアプリケーションがすべてのテナントの認証情報を手に入れられます。tenant-pass が防ごうとしているのは、まさにそれです。発行者の鍵は、別にデプロイし、別に守っている部品に置きます。

設定

"job_tickets": {
  "signing_key": {"id": "tp-jobs-2026-11", "transit": {"key": "tenant-pass-jobs", "version": 1}},
  "trusted_issuers": [{"id": "scheduler-1", "public_key_file": "/etc/tenant-pass/scheduler-1.pub"}],
  "max_ttl": "1h"
},
"apps": [
  {"name": "billing", "token_sha256": "…", "jobs": {"issue": true, "max_ttl": "30m", "max_tenants_per_minute": 60}},
  {"name": "reports", "token_sha256": "…", "jobs": {"trusted_issuers": ["scheduler-1"], "max_tenants_per_minute": 600}}
]

jobs のないアプリケーションは、チケットを手に入れることも、認証情報と交換することもできません(403 denied)。jobs.issue を書いたアプリケーションだけが POST /v1/job-tickets を呼べます。

jobs.trusted_issuers には、そのアプリケーションが受け取ってよい外部の発行者を並べます。並べていない発行者のチケットは拒否します(403。監査ログでは issuer_not_allowed)。tenant-pass 自身の鍵で署名したチケットは、ここに書かなくても受け付けます。tenant-pass は、チケットに書かれたアプリケーション以外には発行しないからです。

チケットの有効期間(exp − iat)は、アプリケーションの jobs.max_ttl 以下、かつ job_tickets.max_ttl 以下でなければなりません。jobs.max_ttl は発行のときにも交換のときにも確かめ、外部の発行者のチケットにも当てはめます。どちらの上限も、ジョブに必要な範囲でできるだけ短くしてください。

詳しくは 設定リファレンス をご覧ください。

チケットの形式

作りはパスと同じです(パスの形式(tenant-pass/v1))。違うのは次の 4 点だけです。

  • 暗黙のアサーションは tenant-pass/v1/job です。
  • フッターは {"kid": "<issuer key ID>"} です。kid は、tenant-pass 自身の signing_key.id か、trusted_issuers[].id のどれかです。
  • クレームは aud(アプリケーション)、tenant(テナント ID)、iat、exp、jti だけです。host はありません。
  • exp − iat ≤ job_tickets.max_ttl です(既定は 1 時間、どの設定でも上限は 24 時間)。

セキュリティ上の性質と制限

チケットはシークレットとして扱う

チケットは、期限が切れるまで、1 つのテナントと 1 つのアプリケーションについての権限そのものです。持っている人なら誰でも使えます。ジョブのデータの中にだけ置き、ログには決して出さないでください(tenant-pass もログに出しません)。チケットはキューにも置かれるので、キューも守る必要があります。

個別には無効にできない

特定のチケットだけを無効にする仕組みはありません。止めたいときは、発行者の鍵を設定から外して反映する(tenant-pass 自身の鍵なら signing_key を入れ替える)か、ディレクトリでテナントを無効にします。

また、チケットで取得した認証情報にも通常のウィンドウの決まりが当てはまります。そのため、チケットの期限より最大で 1 ウィンドウ分長く使えることがあります。

発行を許すアプリケーションと発行者は絞る

jobs.issue を持つアプリケーションが乗っ取られると、正しく受け取ったパスをチケットに替えて、そのテナントを扱える期間を数秒から jobs.max_ttl まで延ばせてしまいます。issue は必要なアプリケーションにだけ与え、max_ttl は短くしてください。

発行者についても同じです。あるアプリケーションに並べた発行者は、そのアプリケーションが扱えるどのテナントのチケットにも署名できます。

レート制限は別枠

ジョブ用のレート制限は、パスとは別に数えます。アプリケーションは、パスで最大 limits.max_tenants_per_minute 個のテナントを扱い、それとは別にチケットで jobs.max_tenants_per_minute 個のテナントを扱えます(どちらもレプリカごと)。ジョブ用の上限は、ジョブが本当に必要とする値にしてください。

1 つのパスから 1 枚まで

1 つのパスと交換できるチケットは 1 枚だけで、2 回目は 403(監査ログでは pass_already_used)になります。tenant-pass は、パスの ID をそのパスの期限が切れるまで覚えておきます。ただし覚えるのはレプリカごとなので、レプリカが N 台なら最大 N 枚まで出てしまいます。

ディレクトリのエラーや無効なテナントなど、ほかの理由で発行に失敗したときは、パスは使われなかったものとして扱うので、やり直せます。

パスの ID を覚えておく領域には、アプリケーションごとに上限があり、1 つのアプリケーションがほかの分まで使い切ることはありません。いっぱいになるとチケットの発行を拒否しますが、同じパスで認証情報を取ることは引き続きできます。

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