Claude Media
Claude apps gatewayのprivate_key_jwt設定 — Entraで証明書認証

Claude apps gatewayのprivate_key_jwt設定 — Entraで証明書認証

Entra IDのように証明書資格情報でOAuthクライアントを認証するIdPでは、client_secretの代わりにclient_assertionを渡します。設定、起動拒否の条件、証明書更新の順序まで。

Claude apps gatewayのIdPがOAuthクライアントに証明書資格情報を使わせる場合は、oidc.token_endpoint_auth_method: private_key_jwtを指定し、client_secretの代わりにclient_assertionブロックで秘密鍵と証明書を渡します。Microsoft Entra IDが代表例です。ゲートウェイ側のClaude Codeはv2.1.284以降が必要です。

秘密文字列を一切送らず、サインイン時とセッション更新時に、証明書の秘密鍵で署名した短命のJWTでトークンエンドポイントへ認証する方式です。設定は数行ですが、鍵の形式や書き方を間違えるとゲートウェイが起動しません。ここでは手順と、起動拒否の条件、証明書の入れ替え順を扱います。

client_secretとprivate_key_jwtは何が違うのか

どちらもゲートウェイがIdPのトークンエンドポイントに自分を証明する方法です。違いは、証明に使う材料です。

くらべる

トークンエンドポイントへの認証方式

共有の秘密文字列

client_secret系

client_secret_basicかclient_secret_post。どちらを使うかは、token_endpoint_auth_methodを省くとIdPが示す対応方式から自動で選ばれます。

証明書と秘密鍵

private_key_jwt

秘密は送りません。サインインのたび、またセッション更新のたびに、秘密鍵で署名した短命のJWTを送ります。

JWTの署名はRS256です。ヘッダーにはkidではなくx5tとx5t#S256の拇印(サムプリント)が入るため、IdPは登録済みの証明書を拇印で探せる必要があります。Entra IDの証明書資格情報はこの条件に合う、というのが設定ページの前提です。

token_endpoint_auth_methodの指定が要るのは、証明書方式を選ぶときだけです。省いたままだと、ゲートウェイはclient_secret系の2方式のどちらかを選ぶので、client_assertionを書いても使われません。

設定の手順

証明書認証への切り替えは、鍵と証明書の作成、IdPへの証明書登録、gateway.yamlへの記述、再起動後の拇印照合の4段階です。途中を飛ばしたときの失敗は、各段階の末尾に書きます。

鍵と証明書を作る

秘密鍵はパスフレーズなしで、PKCS#8かPKCS#1のPEM形式にします。2048ビット未満の鍵や、この条件を外れた鍵ではゲートウェイが起動しません。次のコマンドは、有効期間1年の自己署名証明書つきで条件に合う鍵を作ります。

openssl req -x509 -newkey rsa:2048 -nodes \
  -keyout idp-client.key -out idp-client.crt \
  -days 365 -subj "/CN=claude-gateway"

カレントディレクトリにidp-client.keyとidp-client.crtができます。両方をゲートウェイが読める場所に置くかマウントします。以降の例は/etc/gateway/を使います。

ここで作る証明書の有効期間は-days 365の1年です。期限はあとで起動ログに表示されるので、更新の予定を立てる起点になります。

証明書だけをIdPへ登録する

IdPのアプリ登録にアップロードするのは証明書(.crt)です。秘密鍵(.key)は渡しません。

この登録を忘れたままゲートウェイを切り替えると、IdPには署名の検証に使える証明書がありません。IdPは拇印で登録済みの証明書を探すので、ゲートウェイが署名に使う証明書と同じものが、IdP側に載っている必要があります。

gateway.yamlに書く

Entraのテナントに証明書で認証するoidcブロックは、次の形になります。

oidc:
  issuer: https://login.microsoftonline.com/<tenant-id>/v2.0
  client_id: <application-id>
  token_endpoint_auth_method: private_key_jwt
  client_assertion:
    private_key_pem: ${file:/etc/gateway/idp-client.key}
    certificate_pem: ${file:/etc/gateway/idp-client.crt}

private_key_pemとcertificate_pemに入れるのは、ファイルのパスではなくPEMの中身です。マウントしたファイルは${file:/path}で読み込みます。この記法は、フィールドの値全体がその参照であるときだけ展開されます。長い文字列の途中に埋め込む使い方はできません。

client_secretは書きません。両方あるとゲートウェイは起動を拒否します。client_secretが必須になるのは、token_endpoint_auth_methodがprivate_key_jwtでない場合だけです。逆にclient_assertionは、private_key_jwtを選んだときに必須のブロックです。

${file:}と${VAR}の違い

gateway.yamlに秘密を直書きせず、起動時に解決させる記法は2つあります。

記法展開されるもの展開の条件
${VAR}展開されるもの環境変数VARの値展開の条件未定義だと起動に失敗する
${file:/path}展開されるもの絶対パスのファイルの中身(前後の空白は除かれる)展開の条件フィールドの値全体が参照であること。長い文字列の途中では展開されない

PEMは複数行のファイルなので、鍵と証明書は${file:}で読ませるのが公式の例の形です。${file:}が値全体でなければならない点は、private_key_pemに前置きの文字を足したくなったときに引っかかります。

Entraの接続では、ほかに2点がoidcブロックの設定になります。Entraはグループ名ではなくオブジェクトIDを返すため、ポリシー側にはGUIDを書きます。可読な名前で扱いたいときはアプリロールを使う手があり、その場合の値はrolesクレームに出るのでgroups_claim: rolesを指定します。

再起動して拇印を照合する

再起動し、起動ログに次の行が出ているかを見ます。

[gateway] 2026-10-01T23:07:40.512Z info oidc: client authentication private_key_jwt; certificate CN=claude-gateway, SHA-1 thumbprint DE92821854EE8BAA1D98C758FAA04AABE80B9F57, expires Oct  1 23:07:31 2027 GMT

ここに出るSHA-1拇印を、IdPがアップロード済みの証明書として表示する拇印と見比べます。一致すれば、IdPが探す証明書と、ゲートウェイが署名に使う証明書は同じものです。再起動の前に手元で拇印と期限を確かめておく場合は、証明書ファイルに対して次のコマンドが使えます。

openssl x509 -in idp-client.crt -noout -enddate -fingerprint -sha1

notAfterの日付が起動ログのexpiresに、SHA1のフィンガープリントがSHA-1拇印にあたります(表記は区切りのコロンの有無などが違います)。ここで期限や拇印が想定と違えば、ゲートウェイを動かす前に気づけます。最後に開発者の1人にゲートウェイ経由でサインインしてもらうと、IdPが証明書を受け入れたことまで確かめられます。

起動しない・失敗するときの切り分け

証明書方式のエラーは、大半が起動時の検査で出ます。設定ページに書かれた拒否条件を、症状から引ける形にまとめます。

症状条件
ゲートウェイが起動しない条件client_secretとprivate_key_jwtを同時に設定している
ゲートウェイが起動しない条件秘密鍵が暗号化されている、2048ビット未満、PKCS#8/PKCS#1のPEMではない
ゲートウェイが起動しない条件certificate_pemにチェーンの残りも入っている(単一のPEM証明書でなければならない)
ゲートウェイが起動しない条件certificate_pemの公開鍵がprivate_key_pemと対応していない
起動はするが、サインインと更新が失敗する条件証明書の期限切れ、またはまだ有効期間に入っていない(起動時に警告ログが出る)

最後の行だけは、起動を止めません。警告ログを見逃すと、開発者側の症状だけが先に出ます。期限の控え方は、後半の「期限切れを防ぐ監視の置き方」にまとめました。

サインイン後のエラー側の切り分けは、gatewayのログインエラー3種にまとめています。

証明書を更新する順序

ゲートウェイは鍵と証明書を起動時に1度だけ読みます。ファイルを差し替えただけでは反映されず、再起動が必要です。トークン要求がIdPの知らない証明書を提示する瞬間を作らないために、次の順に進めます。

  1. 新しい証明書を、古いものと並べてIdPへ登録する
  2. gateway.yamlが読む鍵と証明書のファイルを差し替え、ゲートウェイを再起動する
  3. 全レプリカの再起動が終わったら、古い証明書をIdPから外す

複数レプリカでも、ローリング再起動で足ります。IdPには手順3まで両方の証明書があるので、新旧のレプリカが混在しても署名は通ります。ローリング再起動中の挙動はデプロイと運用の解説で扱っています。

手順1と3の間に時間がかかっても、IdPに両方の証明書が登録されている間は、新旧どちらの証明書で署名しても通ります。失敗の型は逆の2つです。古い証明書を先に外す、あるいは新しい証明書の登録前にゲートウェイを再起動すると、IdPが知らない証明書でトークン要求が届きます。

期限切れを防ぐ監視の置き方

証明書の期限が切れると、ゲートウェイは起動はしますが、サインインとセッション更新が失敗します。警告ログが出るのは起動時で、鍵と証明書は起動時に1度しか読まれません。期限を知らせてくれる仕組みは、運用側で用意する形になります。

控える日付は、起動ログのoidc: client authenticationの行にあるexpiresです。導入した日に、この日付を次の3つに写しておきます。

  • 更新作業の開始日: expiresの何日か前。手順1のIdP登録から手順3の削除まで、変更承認や全レプリカの再起動を含めて終わる日数を見込みます。30日前は一つの目安です
  • 古い証明書をIdPから外す日: 手順3の予定日
  • 更新後の確認日: 再起動後の起動ログで、新しいexpiresと拇印を見る日

更新後に控え直すのも同じ行です。拇印とexpiresが新しい証明書のものに変わっていれば、差し替えが反映されています。

IdP通信とプロキシの関係

証明書認証と無関係に見えて、実際には同じoidcブロックの設定が絡む場面があります。ゲートウェイからIdPへの通信、つまりディスカバリー、JWKS、トークン、userinfoは、oidc.use_proxy: trueを設定しない限り、プロキシを通さず直接出ます。プロキシ越しにしか外へ出られない環境では、トークンエンドポイントへの証明書付き要求もこの経路に乗ります。use_proxyはv2.1.227以降の項目で、HTTPS_PROXYかHTTP_PROXYの値を使い、NO_PROXYに載せた宛先は直接つなぎます。社内CAを使うIdPでは、PEMの中身を渡すca_cert_pem(${file:/etc/gateway/idp-ca.pem}の形で読み込めます)で、IdPへの通信だけ信頼するCAを置き換えられます。Azure ADからADFSへフェデレーションする構成のように、認可リクエストが別のホストを経由するIdPでは、form_action_originsに経由先のオリジンを足す設定もあります。条件はproxy経由だけの環境での設定に詳しく書きました。

既存のSSO構成との関係

private_key_jwtが変えるのは、ゲートウェイがIdPへ自分を証明する部分だけです。allowed_email_domainsやallowed_groups、groups_claimなど、誰がサインインできるかを決める設定はそのまま使います。Entraではグループ名ではなくオブジェクトIDが出るため、managed.policies.match.groupsにはGUIDを書きます。テナントがグループをgroupsでなくrolesに出すときは、oidc.groups_claim: rolesを指定します。

設定項目の全体はgateway.yamlのリファレンスに、IdPごとの違いはSSOのIdP比較にあります。

まとめ

証明書認証への切り替えで確認するのは、3点です。client_secretを残していないこと、certificate_pemが単一の証明書であること、起動ログの拇印がIdP側と一致すること。更新時は、新しい証明書の登録、再起動、古い証明書の削除の順を守れば、サインインを止めずに入れ替えられます。

この記事を共有:XはてブLinkedIn