デプロイ
tenant-pass は、内部のネットワークのロードバランサーの後ろで、2 つ以上のレプリカを動かします。tenant-pass に接続できるのは、アプリケーションと監視の仕組みだけにしてください。
配布しているコンテナイメージは distroless で、root 以外のユーザーで動き、シェルも入っていません。これを、ルートのファイルシステムを読み取り専用にし、追加のケイパビリティ(Linux の特権)を与えずに動かします。シークレットはファイルとしてマウントします(Docker / Swarm のシークレット、Kubernetes のシークレット、systemd の認証情報など)。
rotation、nodes[].name、ディレクトリの設定は、すべてのレプリカで同じにします。設定を変えたら、全レプリカに反映してください。
サーバーの時計は NTP で合わせておきます。ずれは renew_before よりずっと小さく、分ではなく秒の単位に収まっている必要があります。
ヘルスチェックには、生きているかの確認(ライブネス)に GET /healthz を、リクエストを受けられるかの確認(レディネス)に GET /readyz を使います。停止を待つ時間は shutdown.drain + shutdown.timeout より長くしてください(既定値のままなら 20 秒が目安です)。
Docker Compose なら、たとえば次のようになります。
services:
tenant-pass:
image: ghcr.io/kobesoft-labs/tenant-pass:<version>
command: ["serve", "-config", "/etc/tenant-pass/config.json"]
read_only: true
cap_drop: [ALL]
security_opt: ["no-new-privileges:true"]
stop_grace_period: 20s
volumes:
- ./config.json:/etc/tenant-pass/config.json:ro
secrets: [tenant-pass.key, rotation-2026-11.key, pg1-admin-password]
シークレットのファイルと、信頼の判断に使うファイル(公開鍵や CA の証明書など)は、root かコンテナを動かすユーザー(イメージは uid 65532 で動きます)の持ち物にし、グループやほかのユーザーが書き込めないようにします。Docker と Kubernetes でマウントしたシークレットは、既定で root の持ち物になっているので、そのままで条件を満たします。
最初の構築
- ストアの管理者アカウントと、管理対象の目印にするロールを用意します。tenant-pass がストアにログインし、扱ってよいアカウントを見分けるためです(PostgreSQL、MySQL、Redis)。
tenant-pass gen-rotation-key -out rotation-YYYY-MM.keyでローテーションキーを作り、tenant-pass のレプリカにだけ配ります。拡張子の.keyは変えないでください。*.keyのファイルは、git と Docker のビルドの対象から外れるようになっています。tenant-pass gen-gateway-key …でゲートウェイの鍵ペアを作ります。秘密鍵はゲートウェイに、公開鍵は tenant-pass に渡します。- アプリケーションごとに
tenant-pass gen-app-tokenでアプリトークンを作ります。トークンはアプリケーションのシークレットの保管場所に、ハッシュは設定ファイルに入れます。 tenant-pass check-config -config config.json -connectで、設定と接続に問題がないことを確かめます。- tenant-pass を起動し、アプリケーションをテナントごとに切り替えていきます。各アカウントのそれまでのパスワードは、そのアカウントが tenant-pass 経由で初めて使われたときに置き換わります。切り替えが済んだら、利用者のシステムに保存していた古いパスワードを削除してください。
ローテーションキーの入れ替え
鍵には not_before(使い始める時刻)があります。この時刻を基準に切り替わるので、レプリカ同士で連絡を取り合わなくても、全レプリカが同じウィンドウで新しい鍵に移ります。
tenant-pass gen-rotation-key -out rotation-2026-12.keyで新しい鍵を作ります。- 新しい鍵を、すべてのレプリカの設定に追加します。
not_beforeには、設定の反映が終わる時刻より十分後の時刻(たとえば翌日の 03:00 UTC)を指定します。"keys": [ {"id": "r-2026-11", "secret_file": "/run/secrets/rotation-2026-11", "not_before": "2026-11-01T00:00:00Z"}, {"id": "r-2026-12", "secret_file": "/run/secrets/rotation-2026-12", "not_before": "2026-12-01T03:00:00Z"} ] - 待ちます。
not_before以降に始まるウィンドウから、新しい鍵が使われます。ほかに何かが起きることはありません。 not_before + window + renew_beforeが過ぎたら、古い鍵を設定から削除して、もう一度反映します。
すでにある鍵の id やシークレットは、決して書き換えないでください。どのウィンドウにどの鍵を使うかは、全レプリカで一致していなければならないからです。一方、not_before が未来の鍵を足すのは、レプリカを順番に更新している最中でも安全です。その時刻までは、どのレプリカも新しい鍵を使わないからです。反映だけは not_before より前に終わらせてください。
鍵が漏れたとき
not_before を「今の時刻 + 反映にかかる時間」にして新しい鍵を追加し、反映します。古い鍵を消すのは、新しい鍵の not_before が少なくとも 1 ウィンドウ分過去になってからにしてください。起動するとき、設定の中でいちばん古い鍵が 1 ウィンドウ全体で有効かどうかを確かめるので、早く消しすぎると起動できなくなります。
古い鍵から導出したパスワードが使えなくなるのは、PostgreSQL ではそのウィンドウが終わったとき、MySQL と Redis では次の発行かスイープで置き換えられたときです。PostgreSQL で今すぐ止めたいなら、対象のロールに ALTER ROLE … VALID UNTIL 'now' を実行します(クライアントは認証情報を取り直します)。期限が切れても開いている接続は閉じられないので、既存の接続も切ってください(方法は下の「侵害が疑われるときの対応」にあります)。
ウィンドウの長さの変更
rotation.window は、全レプリカを一度止めて変えます。手順は、すべてのレプリカを止め、新しい設定をデプロイし、起動するだけです。クライアントは認証情報を 1 回取り直すことになります。
順番に更新してはいけない理由は、window を変えるとすべてのアカウントのウィンドウの番号とオフセットが変わるからです。新旧のレプリカが混ざっている間は計算が食い違い、互いに相手の設定を「tenant-pass が設定したものではない」と見なして上書きし合ってしまいます。
監視
イベントはすべて、1 行に 1 つの JSON として標準出力に出ます。アラートを設定しておきたいのは、次の条件です。いちばん大事なのは先頭の account_refused です。
| 条件 | 意味 |
|---|---|
msg=credential かつ reason=account_refused | ディレクトリが、ストアのチェックで拒否されるアカウント(特権を持つ、管理対象ではない、存在しない)を指しています。設定の間違いか、ディレクトリへの攻撃です。すぐに調べてください。 |
reason=pass_signature または pass_unknown_key | 偽造されたパスか、tenant-pass が知らない鍵を使っているゲートウェイがあります。 |
reason=pass_audience | あるアプリケーションが、別のアプリケーション向けのパスを使おうとしました。 |
reason=distinct_tenants(429) | あるアプリケーションが、異常に多くのテナントの認証情報を求めています。乗っ取られている可能性があります。 |
reason=clock_skew | あるレプリカの時計と、ストアのサーバーの時計の差が max_clock_skew を超えています。そのレプリカは書き込みを拒否します。レプリカかデータベースの NTP を直してください。 |
reason=directory_error / store_error / timeout | ディレクトリやストアの側に問題があります。 |
ERROR レベルの msg=sweep | MySQL / Redis のスイープ(古いパスワードの削除)が失敗しています。期限が切れたパスワードが残っています。 |
reason=distinct_tenants_jobs | あるアプリケーションのジョブが、ジョブ用の上限を超える数のテナントの認証情報を求めています。 |
app ごとの msg=job_ticket の件数 | 発行したジョブチケットの件数です。いつもと違う量になったらアラートを出してください。 |
error に kms を含む | Vault / OpenBao に接続できないか、要求を拒否されています。キャッシュにないパスワードを導出できません。 |
app ごとの result=ok の頻度 | アプリケーションごとの普段の値です。そこから大きく外れたらアラートを出してください。 |
/readyz の失敗 | ディレクトリやストアに接続できないか、レプリカが停止の準備中です。 |
侵害が疑われるときの対応
| 侵害が疑われるもの | 対応 |
|---|---|
| アプリケーション | 設定の apps からそのアプリケーションを消すか、トークンのハッシュを置き換えて反映します。これで新しい認証情報は取れなくなります。ただし、手元に残った認証情報は、最大で 1 ウィンドウと renew_before の間(MySQL / Redis ではさらにスイープ 1 回分)新しい接続に使えます。開いている接続は期限が切れても閉じられないので、対象のアカウントの接続を切ってください(PostgreSQL では SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE usename = 't_acme'、MySQL では SHOW PROCESSLIST で調べて KILL <id>、Redis ではすべてのサーバーで CLIENT KILL USER t_acme)。どのテナントの認証情報が取られたかは、そのアプリケーションの msg=credential のログでわかります。 |
| ゲートウェイの鍵 | gateway_keys からその鍵を削除して反映し、ゲートウェイには新しい鍵をデプロイします。 |
| ジョブの発行者(スケジューラー)の鍵 | job_tickets.trusted_issuers から削除して反映します。その鍵で署名したチケットは、すぐに使えなくなります。 |
| tenant-pass 自身のジョブ用の鍵 | job_tickets.signing_key を新しい id の鍵に置き換えて反映します。発行済みで期限内のチケットも使えなくなるので、ジョブは新しいチケットを手に入れる必要があります。 |
| Vault のトークン | Vault でそのトークンを無効にします。そのホストからは鍵を使えなくなります。 |
| ローテーションキー | 上の「鍵が漏れたとき」の手順で入れ替えます。あわせて、ストアのログに、思い当たらないアドレスからのログインがないか確かめてください。 |
| tenant-pass のホスト | すべてのテナントの認証情報が漏れたものとして扱ってください。ローテーションキー、ストアの管理者のパスワード、TLS の鍵を入れ替え、ストアのログを確かめてください。 |
| ストアの管理者のパスワード | ストアとシークレットのファイルの両方でパスワードを変え、レプリカを再起動してください。 |
リリースされたイメージの確かめ方
デプロイする前に、イメージの署名を確かめてください。リリースのイメージは GitHub Actions のリリース用のワークフローでビルドし、SBOM(含まれる部品の一覧)と SLSA の来歴情報(どこでどうビルドしたかの記録)を付けて公開しています。署名には、鍵を管理しない方式の Sigstore を使っています。
cosign verify ghcr.io/kobesoft-labs/tenant-pass:<version> \
--certificate-identity-regexp '^https://github.com/kobesoft-labs/tenant-pass/\.github/workflows/release\.yml@refs/tags/v' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com
ghcr.io/kobesoft-labs/tenant-pass-gateway も同じように確かめます。デプロイでは、タグではなくダイジェスト(ghcr.io/…@sha256:…)でイメージを指定してください。
アップグレード
リリースノートに断りがなければ、隣り合うバージョンのレプリカは混在させて動かせます。順番に更新してかまいません。パスワードの導出方式とパスの形式にはバージョン(tenant-pass/v1)が付いています。これらを変えるときは、新しいバージョンの名前を付け、移行の手順も一緒に公開します。
この文書は、リポジトリにある docs/ja/operations.md をもとにしています(英語版は docs/en/operations.md)。