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: 承認ルールの
グループIDにagency-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 で取りました。
- 先に Microsoft をサインアウト(ログインを対話的にして時間を稼ぐ)
- ログインタブで F12 → Network →「Preserve log」をログイン前にON
- ログイン後、
127.0.0.1(document, POST)の Payload →SAMLResponseの値を Copy value - その値をコピーし、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つ、決定的なことが分かりました。
memberOfはちゃんと出ている(クレーム設定は正しかった)。- でも値が
2222...で、承認ルールに入れたagency-aの1111...ではない。
なぜ別の 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-a(1111...)は、乗らない。
ずっと「自分が入っているグループ」を確認していたつもりが、それはテナントA側の自分。実際に VPN でログインしていたのはテナントB側のゲストの自分で、別人を設定しているのが原因でした。
直し方:アプリと同じテナントにグループを作る
やることはシンプルで、グループを「トークンを発行するテナント(=Issuer のテナント=テナントB)」に作り直すだけです。
- ポータルを テナントB(アプリのある側)に切り替え(右上アカウント →「ディレクトリの切り替え」)
- テナントB で セキュリティグループ
agency-aを新規作成 - そのグループに自分を追加
- 作ったグループの Object ID(GUID) を、AWS の承認ルールの
グループIDに設定 - 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 の値・identityprovider・Issuer を見れば、「どのテナントの・誰として・どのグループでログインしているか」が一目で分かります。グループ承認で詰まったら、設定確認より先に(あるいは並行して)アサーションを覗くのが、結局いちばんの近道かなと思います。