tenant-pass

02Threat model

セキュリティ

tenant-pass が誰から何を守り、どこから先は守れないのかをまとめました。もとにしたのは脅威モデルです。守れない範囲も、導入を決める前に知っておいてください。

いまの状態

v0.1.0 は最初の公開バージョンで、1.0 より前です。外部のセキュリティ監査はまだ受けていません。デプロイする前に、必ずこのページと脅威モデルの全文をお読みください。

Assets

守るもの

  1. 01テナントのデータストア(PostgreSQL・MySQL・Redis)の中のデータです。守る対象そのものです。
  2. 02テナントの認証情報ウィンドウ(パスワードを入れ替える時間の区切り)ごとのパスワードです。
  3. 03ローテーションキーこれを持つ者は、そこから導出されるすべてのパスワードを計算できます。
  4. 04ストアの管理者の認証情報tenant-pass がパスワードを設定するために使うアカウントです。
  5. 05ゲートウェイの署名鍵これを持つ者は、パスを発行できます。
  6. 06アプリトークン・クライアント証明書アプリケーションが自分を証明するためのものです。

Trust assumptions

何を信頼し、何を信頼しないか

tenant-pass のホストとプロセス

信頼する

ローテーションキーとストアの管理者の認証情報を持ちます。

ゲートウェイ

信頼する

そのテナントについて自分で認証したリクエストにだけ、パスに署名すると信頼します。

ディレクトリ

信頼する

テナントを正しいアカウントに対応付けると信頼します。誤っていた場合の被害は、ストアでの安全性チェックで抑えます。

ストア

信頼する

設定したパスワードと有効期限のとおりにログインを制限すると信頼します。

ネットワーク

信頼しない

信頼しないので、すべての接続で、証明書とホスト名を検証する TLS を使います。

アプリケーション

信頼しない

乗っ取られたアプリケーションにできることを制限するのが、設計の目標です。

時計

おおむね同期

NTP で同期していることを前提にします。ずれは renew_before より十分小さくしてください。

Primary threat

主な脅威:
乗っ取られたアプリケーション

攻撃者が、アプリケーションのプロセスの中でコードを動かせるようになった場合です(リモートでのコード実行、悪意のある依存パッケージ、盗まれたデプロイ用の認証情報など)。tenant-pass がなければ、アプリケーションはふつう全テナントの認証情報(またはそれを復号する鍵)を持っているので、攻撃者はすぐに全テナントのデータを読めます。tenant-pass があれば、攻撃者がアプリトークンを手に入れても、できることは次のように限られます。

任意のテナントの認証情報を要求する

失敗する

パスが必要で、パスに署名できるのはゲートウェイだけです。

パスを偽造する

失敗する

アプリケーションは、ゲートウェイの秘密鍵を持っていません。

別のアプリケーションから入手したパスを使う

失敗する

パスの aud が、認証したアプリケーションと一致しなければなりません。

正規に受け取ったパスを使い回す

限られた範囲で成功する

そのテナントに限り、パスの期限(exp、数秒後)までです。

同じアプリケーションのほかのワーカーが持つ認証情報を読む

限られた範囲で成功する

メモリ内に有効な認証情報があるテナント、つまりいま処理中のテナントだけです。それも 1 ウィンドウ以内に失効します。

保存されたテナントのパスワードを読む

失敗する

パスワードは何も保存されていません。

ゲートウェイに、多くのテナントのリクエストを自分へ転送させる

限られた範囲で成功する

下の「ゲートウェイに関する残存リスク」をご覧ください。1 分あたりのテナント数の制限で上限がかかります。tenant-pass-gateway を使う場合は、テナントごとに有効なユーザーのセッションが必要です。

パスを、有効期間の長いジョブチケットに変える

限られた範囲で成功する

アプリケーションの設定で jobs.issue を有効にしている場合だけです。正規にパスを受け取ったテナントに限られ、有効期間は jobs.max_ttl までです。

自分でジョブチケットに署名する

失敗する

発行者の鍵を持つのは、tenant-pass と、別に運用するスケジューラーだけです。

tenant-pass に管理者アカウントを指させる

失敗する

アカウントを決めるのはディレクトリです。また、ストアでの安全性チェックが、特権を持つアカウントと管理対象外のアカウントを拒否します。

つまり、乗っ取られたアプリケーションから漏れるのは「すべてのテナントを一度に」ではなく、攻撃者が居座っている間に実際に使われているテナントだけです。その増え方も limits.max_tenants_per_minute で抑えられます。

監査ログの量や rate_limited のイベントで異常に気付ければ、対応する時間を稼げます。

Residual risk

ゲートウェイに関する残存リスク(防ぎきれないリスク)

パスが証明するのは、「ゲートウェイがこのテナントのリクエストをこのアプリケーションに転送した」ということだけです。攻撃者が、任意のテナントのリクエストを、乗っ取ったアプリケーションへ転送するようにゲートウェイを仕向けられれば(たとえば、公開されているゲートウェイに Host: victim.example.com を送る)、そのテナントの有効なパスを手に入れられます。

そのため、ゲートウェイがパスに署名してよいのは、そのテナントについてエンドユーザーのセッション(またはジョブ自身の署名つきの認可)を認証した後だけです。ログインページ、ヘルスチェック、公開の静的ファイル、Webhook など、認証を必要としないエンドポイントにパスを渡してはいけません。アプリケーションが内部のネットワークからゲートウェイを直接呼べる構成も避けてください。呼べてしまうと、アプリケーションが好きなホスト名を指定して、自分でパスを取りに行けます。ネットワークの設定で塞いでおきます。

ゲートウェイが、認証していない通信にもパスを署名してしまう構成では、tenant-pass の効果は「アプリケーションは、ホスト名を知っている任意のテナントの認証情報を、制限された速さで取れる」という程度まで下がります。パスワードを保存した一覧よりはましですが、意図した守りではありません。

Session replay

セッションのトークンを使い回される

ゲートウェイの後ろのアプリケーションは、ふつうの Web アプリと同じく、利用者のセッションの Cookie やトークンを受け取ることがあります。乗っ取られたアプリケーションは、それを使い回したり、利用者のブラウザーにほかのテナントのホストを開かせたりして、利用者が属するテナントのパスを集めようとするかもしれません。

  • ・forward_claims: トークンをアプリケーションに渡さず、検証済みの利用者 ID だけを渡します(使い回しの手口をなくせます)
  • ・呼び出し元の認証: ゲートウェイを直接呼ばせません(ループバック以外の待ち受けでは必須)
  • ・セッションをテナントごとに: ホスト限定の Cookie(__Host-)と single_tenant_tokens。ブラウザーを使った手口を防げるのはこれだけです
  • ・Fetch Metadata(ブラウザーが付ける Sec-Fetch-* ヘッダー)の検査は、利用者の操作によらずにブラウザーが送る、ホスト間のリクエストを防ぎます。ただし、ほかのテナントのページも返せる乗っ取られたアプリケーションに対しては、守りになりません

ゲートウェイが守るべき決まりの全文は、ゲートウェイ連携 と tenant-pass-gateway にあります。

Other threats

そのほかの脅威と、その考え方

tenant-pass 自体が乗っ取られた

tenant-pass はローテーションキーとストアの管理者の認証情報を持つため、乗っ取られれば全テナントのデータが危険にさらされます。これは tenant-pass を使わない場合と同じ状況ですが、リスクが「すべてのアプリケーション」から「1 つの小さなサービス」に集まります。そのため、tenant-pass は専用の最小構成で動かしてください。distroless のイメージを使い、root 以外のユーザーで、読み取り専用のルートファイルシステムにして、シェルを入れずに動かします。管理者アカウントは最小権限にし、ネットワークは必要なアプリケーションからだけ届くようにします。ローテーションキーは tenant-pass のシークレットにだけ置きます。

ジョブ発行者(スケジューラー)が乗っ取られた

信頼された発行者は、jobs を設定した任意のアプリケーションについて、任意のテナントのチケットに署名できます。守るべき重さは、ゲートウェイの鍵と同じです。発行者はアプリケーションとは別の場所で動かし、できれば鍵を Vault Transit に置いてください。止めるときは、設定の trusted_issuers から外します。

ローテーションキーが漏れた

鍵を手に入れた者は、その鍵が有効な期間の、過去と未来のすべてのウィンドウのパスワードを、tenant-pass に接続せずに手元で計算できます(ただし、ログインするにはデータベースにネットワークで届く必要があります)。すぐに鍵を入れ替えてください。1 ウィンドウ後には、古いパスワードはすべて無効になります。鍵を Vault / OpenBao Transit に置いていれば、鍵そのものはコピーされません。

ゲートウェイの鍵が漏れた

鍵を手に入れた者は、任意のテナント、任意のアプリケーション宛てのパスを発行できます。ただし、使うにはそのアプリケーションとして認証(アプリトークンや証明書)を通る必要があります。漏れた鍵を設定の gateway_keys から外し、新しい鍵を配ってください。

アプリトークンが漏れた

パスがなければ役に立ちません。設定にあるトークンのハッシュを、新しいトークンのものに置き換えます。

ネットワーク上の攻撃者

アプリケーションから tenant-pass へ、tenant-pass からストアとディレクトリへの、すべての接続で、証明書とホスト名を検証する TLS 1.2 以上を使います。平文で通信するには 2 つの設定を明示する必要があり、試験のためだけに使います。データベースのドライバーは接続文字列(DSN)を使わずに設定するので、ドライバーの安全でない既定値(例: libpq の sslmode=prefer)が紛れ込むことはありません。

通信やログの中のパスワード

データベースに送るのはベリファイアやハッシュだけで、これを使ってもログインはできません。ただし、ベリファイアやハッシュがあれば、手元でパスワードの推測を試みることはできます。tenant-pass のパスワードは 256 ビットのランダムな値なので、推測は現実的ではありません。それでも、管理者ロールについては log_statement = ddl などによる記録を止めておくことをお勧めします。

時計の操作

書き込む前にストアのサーバーの時計と比べ、差が max_clock_skew を超えれば拒否します。時計の狂ったレプリカは、パスワードの期限を未来へ延ばすことも、過去へ戻すこともできません。ただし、ストアのサーバーの時計が狂っていれば tenant-pass はそれに従うので、データベースのサーバーでも NTP で時計を合わせてください。

サービス妨害

リクエストボディは 4 KiB、ヘッダーは 16 KiB までです。読み書きにはタイムアウトがあります。同時に処理するリクエスト数には上限があり、超えるとすぐに 503 busy を返します。1 リクエストの処理時間にも上限(issue_timeout)があります。レディネスの確認結果は 5 秒間キャッシュし、repair による書き直しはアカウントごとに 10 秒に 1 回までです。なお、tenant-pass はインターネットに公開することを想定していません。

ID プロバイダーが乗っ取られた

tenant-pass-gateway は ID プロバイダーが署名したトークンを信頼します。プロバイダー(またはその署名鍵)を握る者は、任意のテナントのセッションを作れます。これは、そのプロバイダーに頼るほかのシステムと同じく、tenant-pass の守りの外です。

Non-goals

目標としないこと

ここに挙げるのは、tenant-pass が守ろうとしていない、または守れないことです(脅威モデルにも書いてあります)。アカウントの用意(プロビジョニング)、データベースの権限設定、運用の手順で補ってください。

  1. 01乗っ取られたゲートウェイ・ディレクトリ・tenant-pass のホスト・ストアから、テナントのデータを守ること。
  2. 02乗っ取られたアプリケーションから、そのアプリケーションが「いま処理している」テナントを守ること。
  3. 03認証済みのアプリケーションから、どのテナントが存在するかを隠すこと(denied は「存在しない」と「許可されていない」を区別しませんが、応答時間で区別できる可能性があります)。
  4. 04ほかのテナントのオブジェクトへの権限付与を検出すること(たとえば、テナント A のスキーマのテーブルを、テナント B のロールに使わせる付与)。テナントの間の分離は、アカウントを用意する側(プロビジョニング)で作ります。
  5. 05テナントが共有のデータベースサーバーの接続数を使い切ることから守ること(管理者ロール用の接続を予約しておいてください。方法は各ストアのガイドにあります)。
  6. 06同じアカウントを指す、ディレクトリの 2 つの行や 2 つのノード名を検出すること(設定ファイルに書くディレクトリはアカウントの共有を拒否しますが、SQL で調べるディレクトリでは利用者のテーブル設計しだいです)。
  7. 07認証情報が切れたときや、アプリケーションを無効にしたときに、すでに開いているデータベースのセッションを閉じること。
  8. 08ストアの中で、テナントのアカウント自身が行う操作から守ること(例: PostgreSQL のロールは自分のパスワードを変えられます。ただし、それで締め出されるのは自分のテナントだけで、次のウィンドウか repair までです)。
  9. 09データベースの権限設定(認可)の代わりになること。テナントのアカウントには、そのテナントのデータに必要な権限だけを与えます。

Compared with a secrets manager

汎用のシークレット管理との違い

tenant-pass は、汎用のシークレット管理の製品ではありません。リクエストに結び付けずに動的な認証情報が必要なら、HashiCorp Vault / OpenBao のデータベースシークレットエンジンを検討してください。両者の違いは「どの場面を対象にするか」です。

tenant-pass汎用のシークレット管理(例: Vault / OpenBao のデータベースシークレットエンジン)
対象マルチテナント SaaS での、テナント単位のデータベースアクセスだけに絞っている種類を限らないシークレットと、動的な認証情報
認証情報を出す条件アプリケーションの認証 に加えて、そのテナントのリクエストが実際に来ていることを示すパスリクエストへの結び付けは求めない
テナントの決め方パスに書かれたホストまたはテナントから、ディレクトリで決める。アプリケーションは選べない—
パスワード保存せず、鍵から計算する。データベースのアカウントは作らず、既存のアカウントのパスワードをローテーションする—
Vault / OpenBao との関係tenant-pass は自分のローテーションキーと署名鍵を Vault / OpenBao の Transit に置けます。競合ではなく、役割の違う道具として組み合わせられます。

「—」は、比較の材料がないため書いていない項目です。汎用のシークレット管理の機能は、製品によって異なります。

Hardening checklist

本番で守ること

  • ✓内部ネットワークだけで使う tenant-pass に届くのは、アプリケーションと監視だけにします
  • ✓すべての接続で TLS 証明書とホスト名を検証します。平文は試験のときだけ
  • ✓管理者アカウントは最小権限に 各ストアのガイドのとおりに設定します
  • ✓シークレットはファイルで渡す 所有者とパーミッションが検査されます
  • ✓レプリカは 2 台以上 ロードバランサーの後ろに置きます
  • ✓NTP で時計を合わせる レプリカとデータベースの両方で
  • ✓アラートを設定する account_refused・pass_signature・rate_limited

運用(監視・インシデント対応)→

Pre-release reviews

リリース前のレビューで直したこと(抜粋)

  • 位数の小さい Ed25519 公開鍵や、正準でない形の公開鍵を拒否するようにしました。このような鍵を受け付けると、誰でもパスを偽造できてしまうためです。
  • 設定ファイルやシークレットだけでなく、公開鍵・CA・証明書・JWKS など「信頼の起点」になるファイルも、所有者とパーミッションを検査するようにしました。ファイルは 1 度だけ開き、開いたファイルそのものを検査します。
  • JSON のキーは、フィールド名と完全に一致しなければならないようにしました。Go の標準の読み込み方は大文字と小文字を区別せずに照合するため、パーサーによって解釈が食い違う原因になるからです。
  • パスワードの導出にウィンドウの長さを含め、rotation.window を変えても、古いパスワードが再び有効にならないようにしました。
  • PostgreSQL 16 以降を必須にし、テナントが接続できない専用の管理用データベースを求めるようにしました。分離の検査は、起動時、レディネスの確認時、そして 5 分ごとに繰り返します。
  • Redis のキーやチャネルのパターンは、固定の文字列(プレフィックス)で始まらなければならないようにしました(~*:secret や ~[a-s]* は拒否します)。

リリースのコンテナイメージには、SBOM(含まれる部品の一覧)と SLSA の来歴(どこでどのようにビルドしたかの記録)が付き、Sigstore で署名されます。デプロイの前に cosign verify で確かめられます(リリースされたイメージの確かめ方)。

Report a vulnerability

脆弱性の報告

公開の Issue は作らないでください。GitHub の非公開の脆弱性報告から送ってください。

非公開で報告する ↗

GitHub を使えない場合は、件名に「tenant-pass security」と入れて dev@kobesoft.co.jp へメールしてください。

  • ・3 営業日以内に、受け付けたことをご連絡します
  • ・10 営業日以内に、評価の結果をご連絡します
  • ・公開する日は、報告者と相談して決めます(通常は報告から 90 日以内)
  • ・ご希望がない限り、公開するセキュリティ情報(アドバイザリ)に報告者のお名前を記載します

特に歓迎する報告

  • ・そのテナントの有効なパスがなくても認証情報を得られる、または別のアプリケーションの認証情報を得られる
  • ・特権を持つアカウントや、管理対象の印がないアカウントを tenant-pass に変更させられる
  • ・設定で明示的に許可していないのに、平文や証明書を検証しない TLS で通信してしまう
  • ・パスワード・鍵・トークンが漏れる(ログ、エラー、応答、処理時間の差)
  • ・パスの形式、PASETO の扱い、パスワードの導出の弱点
  • ・「前にしか進まない」約束が破られる(古いパスワードの復活、期限の延長)

対象外

ゲートウェイ、ディレクトリ、ストアのサーバー、tenant-pass のホストのどれかがすでに乗っ取られている前提の問題は対象外です。安全でないと明記した設定(allow_insecure_plaintext)を使ったときの問題と、認証済みのアプリケーションが設定した上限の範囲内で起こすサービス妨害も、同じく対象外です。なお、1.0 より前は、セキュリティ修正を最新のリリースにだけ出します。