AWS Client VPN を Entra ID(SAML) で グループ単位のアクセス制御 しようとして、ドツボにハマった話です。

承認ルールにグループの GUID を入れて「このグループの人だけ通す」をやろうとしたのですが、Entra のグループ設定も、ユーザーの所属も、GUID も、どこをどう見直しても正しい。なのに、グループ承認だけ延々と timeoutになり、数時間溶かしました。

結論を先に言うと、犯人は 「承認に使うグループが、SAMLアプリ(トークン発行元)と別のテナントにあった」 こと。GUID がどれだけ正しくても、別テナントのグループは memberOf に乗りません。そしてこれは 設定画面をいくら見直しても気づけず、実際の SAML アサーションを覗いて初めて分かりました。

基本構成と「グループ制御の正しいやり方」は別記事にまとめています(記事末尾にリンク)。本記事は ハマり所の切り分けと原因特定 にフォーカスします。

前提:今回やりたいこと

agency-a というセキュリティグループのメンバーだけが、Client VPN 経由で宛先 10.1.0.0/16 の EC2 に到達できる、という制御。

  • Entra: グループクレーム memberOf(セキュリティグループ/ソース=グループID)
  • AWS: 承認ルールの グループIDagency-a の Object ID(GUID)

仕組みとしては「Entra グループの Object ID = memberOf に乗る値 = グループID」の3点一致で動作する、というものです(詳細は別記事にまとめています)。

症状:グループ承認だけ、ずっと timeout

グループ限定の承認ルールを入れて接続し、宛先へTCPで疎通確認すると、

$ curl -v --max-time 5 http://10.1.0.10
*   Trying 10.1.0.10:80...
* Connection timed out after 5006 milliseconds

timeout×♾️。ひたすらtimeoutで届いていない状態に陥りました。

現在の設定を順に見ていきましたが、どれも問題なし:

  • 経路(往路・戻り路・TGWルート・EC2のSG)→ すべてOK
  • グループクレーム設定(memberOf/セキュリティグループ/グループID)→ 正しい
  • 自分がそのグループのメンバーか → 入っている(直接メンバー)
  • グループ所属数 → 2つだけ(オーバーエイジ=150超とは無縁)
  • グループID の GUID → グループの Object ID と一致

……設定上は完璧、なのに弾かれる。ここから切り分けに入ります。

切り分け①:経路は本当に大丈夫か? → 全許可で確認

「承認の問題」か「経路の問題」かを分けるため、承認ルールを一時的に 全ユーザー許可 に差し替えてみました。

再接続して、

$ curl -v --max-time 5 http://10.1.0.10
* connect to 10.1.0.10 port 80 from 172.16.0.x port 64318 failed: Connection refused

refused(到達)。約30msで返ってきました。

Connection refused = EC2まで届いて拒否された=到達の証拠(Webサーバー未起動でも出る)。timeout = そもそも届いていない。で切り分けしています!

これで、経路は完全にOK。timeout の原因は グループ承認=memberOf に GUID が乗っていない ことが判明しました。なので、問題は Entra/SAML 側に絞られました。

切り分け②:もう推測はやめて、実物のアサーションを見る

設定確認の堂々巡りを断ち切るには、実際にブラウザを流れている SAML アサーションを見るのが一番です。今回は DevTools で取りました。

  1. 先に Microsoft をサインアウト(ログインを対話的にして時間を稼ぐ)
  2. ログインタブで F12 → Network →「Preserve log」をログイン前にON
  3. ログイン後、127.0.0.1(document, POST)の Payload → SAMLResponse の値を Copy value
  4. その値をコピーし、Base64デコードして中身(XML)を確認する

デコードすると、こんなアサーション(抜粋・値はダミー)が見えます:

<Attribute Name="http://schemas.microsoft.com/identity/claims/objectidentifier">
  <AttributeValue>99999999-9999-9999-9999-999999999999</AttributeValue>
</Attribute>
<Attribute Name="http://schemas.microsoft.com/identity/claims/identityprovider">
  <AttributeValue>https://sts.windows.net/aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa/</AttributeValue>
</Attribute>
...
<Attribute Name="memberOf">
  <AttributeValue>22222222-2222-2222-2222-222222222222</AttributeValue>
</Attribute>

ここで2つ、決定的なことが分かりました。

  1. memberOf はちゃんと出ている(クレーム設定は正しかった)。
  2. でも値が 2222... で、承認ルールに入れた agency-a1111... ではない

なぜ別の GUID なのか? 答えは他の属性にありました。

  • objectidentifier(ログインした本人のID)が、Entra で確認した自分のIDと違う
  • identityprovider(本人のホームテナント)が テナントA
  • 一方、トークンの発行元(Issuer)は テナントB

種明かし:承認グループと SAML アプリが「別テナント」だった

整理するとこういう構図でした。

要素 どこにあったか
agency-a グループ(1111...)と、自分のメンバーアカウント テナントA(自分のホーム)
AWS ClientVPN アプリ(SAMLトークンの発行元) テナントB
VPNログイン時のトークン発行 テナントB が発行
テナントBから見た自分 ゲスト(B2B) 扱い

VPNログインのトークンは テナントB が発行する。自分はテナントBではゲストで、テナントBの別グループ(2222...)にしか入っていない。だから memberOf に乗るのは テナントB側のグループだけ。テナントAにある agency-a1111...)は、乗らない。

ずっと「自分が入っているグループ」を確認していたつもりが、それはテナントA側の自分。実際に VPN でログインしていたのはテナントB側のゲストの自分で、別人を設定しているのが原因でした。

直し方:アプリと同じテナントにグループを作る

やることはシンプルで、グループを「トークンを発行するテナント(=Issuer のテナント=テナントB)」に作り直すだけです。

  1. ポータルを テナントB(アプリのある側)に切り替え(右上アカウント →「ディレクトリの切り替え」)
  2. テナントB で セキュリティグループ agency-a を新規作成
  3. そのグループに自分を追加
  4. 作ったグループの Object ID(GUID) を、AWS の承認ルールの グループID に設定
  5. AWS Client VPN を切断→再接続(再認証で新しいアサーションを発行させる)

差し替え後、もう一度アサーションを見ると、memberOf新グループのGUIDが乗っています。そして——

$ curl -v --max-time 5 http://10.1.0.10
* connect to 10.1.0.10 port 80 ... failed: Connection refused

到達(refused)。 さらに、そのグループから自分を外して再接続すると、

$ curl -v --max-time 5 http://10.1.0.10
* Connection timed out after 5002 milliseconds

timeout(拒否)。

グループ agency-a(テナントB)の状態 curl の結果 意味
メンバー(自分が所属) Connection refused 到達=許可成立
非メンバー(自分を外す+再接続) timeout 拒否成立

「メンバーなら到達、外せば拒否」が両方向で成立。正しいテナントにグループを置いた瞬間に、何事もなく動きました。

教訓

承認に使うグループは、SAMLアプリ(=トークンを発行するテナント=アサーションの Issuer)と同じテナントに作る。
別テナントのグループは、GUID がどれだけ正しくても memberOf に乗らない。

外部ユーザーをゲスト招待する構成(B2B)では、ゲストを「アプリのテナント側」のグループに入れるのがポイント。「ホームのテナントで作ったグループ」ではダメ、というのが今回の落とし穴でした。

そしてもう一つ、これが一番の学びになりました:

テナント不一致の類は、設定画面を何周見ても気づけない。実アサーションを見て初めて分かる。

memberOf の値・identityproviderIssuer を見れば、「どのテナントの・誰として・どのグループでログインしているか」が一目で分かります。グループ承認で詰まったら、設定確認より先に(あるいは並行して)アサーションを覗くのが、結局いちばんの近道かなと思います。

関連記事

参考