tenant-pass

03Get started

導入

tenant-pass は、Go で書かれた 1 つの実行ファイル(静的バイナリ)です。依存するライブラリは、データベースのドライバー程度です。まず手元の PostgreSQL で、パスが認証情報に変わるところまで 5 つの手順で試せます。本番に導入するまでの流れは、その後にあります。

v0.1.0(1.0 より前)。設定や API の細部は変わる可能性があります。

Try it locally

手元で試す

この手順は平文の接続を使うので、手元での評価専用です。設定ファイルの全文と、わざと失敗させて動きを確かめる方法は、 はじめに(ローカルで試す) にあります。

  1. 1 インストール
    go install github.com/kobesoft-labs/tenant-pass/cmd/tenant-pass@latest
    go install github.com/kobesoft-labs/tenant-pass/cmd/tenant-pass-gateway@latest   # ゲートウェイを使う場合
  2. 2 鍵とトークンを作る
    tenant-pass gen-gateway-key -private gw.key -public gw.pub   # 秘密鍵はゲートウェイにだけ置く
    tenant-pass gen-rotation-key -out rotation.key              # tenant-pass にだけ置く
    tenant-pass gen-app-token                                   # token はアプリへ、token_sha256 は設定へ
  3. 3 データベースにロールを用意する
    CREATE DATABASE tenant_pass;
    REVOKE ALL ON DATABASE tenant_pass FROM PUBLIC;
    CREATE ROLE tenant_pass_admin LOGIN CREATEROLE PASSWORD '...';
    GRANT CONNECT ON DATABASE tenant_pass TO tenant_pass_admin;
    CREATE ROLE tenant_pass_managed NOLOGIN;
    CREATE ROLE t_acme LOGIN IN ROLE tenant_pass_managed;
    GRANT t_acme TO tenant_pass_admin WITH ADMIN OPTION, INHERIT FALSE, SET FALSE;
  4. 4 設定を検査して起動する
    tenant-pass check-config -config config.json -connect
    tenant-pass serve -config config.json
  5. 5 パスを認証情報に交換してみる
    PASS=$(tenant-pass sign-pass -key gw.key -kid gw1 -aud billing -host acme.example.com)
    curl -s http://127.0.0.1:8443/v1/credentials \
      -H "Authorization: Bearer $APP_TOKEN" -H 'Content-Type: application/json' \
      -d "{\"pass\":\"$PASS\"}"
{
  "tenant": "acme", "store": "postgres",
  "host": "127.0.0.1", "port": 5432, "database": "app",
  "username": "t_acme", "password": "lRG6msAkg88kIIjJU-N6yWZ6wzx1gQj62GYG71hcgjE",
  "expires_at": "2026-11-03T12:08:59Z", "refresh_at": "2026-11-03T12:03:59Z"
}

Requirements

対応するもの

ゲートウェイ: 付属の tenant-pass-gateway を、Caddy・nginx(どちらも動作確認済み)・Traefik・Envoy の forward-auth として使えます。独自のゲートウェイに組み込む場合は、Go の pass パッケージか、PASETO v4 のライブラリを使います。

ディレクトリ: テナントとアカウントの対応を設定ファイルに直接書くか、すでにテナントの情報を持つ PostgreSQL / MySQL に独自の SQL クエリで問い合わせます。クエリは読み取り専用のトランザクションで実行し、起動時に検査します。

鍵管理(任意): HashiCorp Vault / OpenBao の Transit に、ローテーションキー(hmac)と署名鍵(ed25519)を置けます。

Rolling out

本番に導入するまで

ゲートウェイの鍵 ローテーションキー アプリトークン

ここで作るのは、ゲートウェイの鍵、ローテーションキー、アプリトークンの 3 つです。秘密にする側は、それぞれゲートウェイ、tenant-pass、アプリケーションにだけ置きます。

  1. STEP 1

    ストアを準備する

    tenant-pass がログインに使う最小権限の管理者アカウントと、「管理対象」の印(PostgreSQL のグループロール、MySQL の印となるロール、Redis のチャネルルール)を用意します。

    ストアごとの手順 →
  2. STEP 2

    ローテーションキーを作る

    tenant-pass gen-rotation-key -out rotation-YYYY-MM.key で作ります。この鍵は tenant-pass のレプリカにだけ配ります(できれば Vault / OpenBao の Transit に置きます)。

    鍵管理 →
  3. STEP 3

    ゲートウェイの鍵を作る

    tenant-pass gen-gateway-key で作ります。秘密鍵はゲートウェイに、公開鍵は tenant-pass の設定の gateway_keys に置きます。

    tenant-pass-gateway →
  4. STEP 4

    アプリトークンを作る

    アプリケーションごとに tenant-pass gen-app-token で作ります。トークンはアプリケーションのシークレットに、ハッシュは設定ファイルに書きます。クライアント証明書(URI SAN)で認証することもできます。

    apps の設定 →
  5. STEP 5

    設定を検査する

    tenant-pass check-config -config config.json -connect を実行します。起動時と同じ検査をすべて行い、ディレクトリとすべてのノードに実際に接続して確かめます。

    設定リファレンス →
  6. STEP 6

    テナントごとに切り替える

    tenant-pass を起動し、アプリケーションをテナントごとに切り替えます。最初に使われたときに、tenant-pass は各アカウントの既存のパスワードを置き換えます。切り替えが済んだら、これまで保存していた古いパスワードを、自社のシステムから消してください。

    最初の構築 →

Application side

アプリケーションで変えること

アプリケーションですることは、ヘッダーで届いたパスを tenant-pass に渡し、返ってきた認証情報をメモリにキャッシュすることです。そのうえで、次の 6 点を守ってください。

  • 認証情報はメモリの中だけに置く

    認証情報を、ディスク、共有キャッシュ(Redis・memcached)、データベース、ログ、エラーレポート、メトリクスのラベルに書かないでください。

  • refresh_at の時刻に取り直す

    refresh_at の時刻から、tenant-pass は次のウィンドウのパスワードを返します。早めに取り直しても害はありません(同じパスワードが返ります)。PostgreSQL では、refresh_at を実質の期限として扱ってください。

  • 認証エラーなら repair を付けて 1 回だけ取り直す

    PostgreSQL の 28P01、MySQL の 1045、Redis の WRONGPASS を受けたら、"repair": true を付けて認証情報を取り直し、1 回だけ再接続します。それでも失敗したら、繰り返さずにエラーにします。

  • 接続プールはテナントごとに分ける

    テナント A のプールをテナント B に使わないでください。使われなくなったテナントのプールは閉じて、認証情報をメモリから消します。

  • テナントは応答から取る

    どのテナントかは、認証情報の応答の tenant から取ります。Host ヘッダーからは取らないでください。

  • エラーの種類で扱いを分ける

    400・401・403 は、同じパスで何度試しても成功しない失敗です。429・503 は、間を空けてリトライしてかまいません。

# リクエストごと(疑似コード)
credential = cache.get(tenant)
if credential is None or now >= credential.refresh_at:
    credential = tenant_pass.fetch(pass)          # POST /v1/credentials
    cache.put(tenant, credential)
conn = connect(credential)
if conn failed with authentication error:
    credential = tenant_pass.fetch(pass, repair=True)
    cache.put(tenant, credential)
    conn = connect(credential)                    # 再接続は 1 回だけ。繰り返さない

パスのないリクエストには、テナントのデータに触れる権限がありません。そのようなリクエストはエラーにしてください。後から動く処理には ジョブチケット を使います。Go の実装例は、 クライアント連携 にあります。

Production

本番の構成

  • ・内部ネットワークのロードバランサーの後ろで、2 台以上のレプリカを動かします。tenant-pass に届くのは、アプリケーションと監視だけにします。
  • ・コンテナイメージ(distroless、root 以外のユーザー、シェルなし)を、読み取り専用のルートファイルシステムで、追加の特権(ケイパビリティ)を与えずに動かします。シークレットはファイルとしてマウントします。
  • ・すべてのレプリカで rotation・nodes[].name・ディレクトリの設定を同じにします。
  • ・時計は NTP で同期します。ずれは分ではなく、秒の単位に収めてください。
  • ・ヘルスチェックには、GET /healthz(動いているか)と GET /readyz(リクエストを受け付けられるか)を使います。
  • ・停止するときの猶予時間を、shutdown.drain + shutdown.timeout(既定で合わせて 20 秒)より長くします。Docker の既定の 10 秒では足りません。
# 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]

シークレットや、信頼の起点になるファイル(公開鍵や証明書など)は、root かコンテナを動かすユーザー(イメージでは uid 65532)の所有にし、グループやほかのユーザーが書き込めないようにしてください。イメージはタグではなくダイジェストで指定し、デプロイの前に署名を確かめます(リリースされたイメージの確かめ方)。

導入・組み込みのご相談

既存のマルチテナント SaaS への組み込み、テナントごとのアカウントへの移行、ゲートウェイの設計などのご相談は、神戸ソフトで承ります。