外部のグループなどに、公開ALBではなく AWS Client VPN でプライベートに、しかも「このグループの人だけ」という形で本番アプリへアクセスさせたい——これは Entra ID(SAML) のグループクレームと Client VPN の承認ルールで実現できます。本記事はその再現手順です。
ポイントを先に1行で言うと——
「Entra のグループ Object ID」=「SAMLの
memberOfの値」=「Client VPN 承認ルールのグループID」 の3つを一致させれば、グループ単位でアクセス制御できる。
なお、この構成では設定は全部正しいのに弾かれる落とし穴(テナント不一致)も踏み、なかなか苦戦しました。。。。。
そちらは別記事にまとめます。本記事は「正しく動かす手順」を中心にまとめています。
構成

| 要素 | 値 |
|---|---|
| 認証 | Microsoft Entra ID(SAML 2.0 フェデレーション) |
| Client VPN VPC | 10.0.0.0/16 |
| Workload VPC | 10.1.0.0/16(EC2 10.1.0.10) |
| 接続 | Transit Gateway |
| クライアント CIDR | 172.16.0.0/22(VPNクライアントのIPプール) |
Client VPN・SAML・TGW の基本構築は済んでいる前提で、本記事は 「グループ制御」の部分にフォーカスします。
仕組み:GUID が3か所で一致すれば制御できる
[Entra] グループ "agency-a"
Object ID = aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa
│
│ SAMLログイン時、所属グループのGUIDが memberOf に入る
▼
[SAML assertion] <Attribute Name="memberOf">
<AttributeValue>aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa</AttributeValue>
│
│ AWS はこの値と承認ルールの グループID を突き合わせる
▼
[AWS Client VPN] 承認ルール グループID = aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa
宛先 = 10.1.0.0/16 → 許可
この aaaa...(=Entra グループの Object ID)が3か所すべてで一致していれば、「そのグループのメンバーだけが宛先に通れる」状態になります。
地味にハマったのが呼び名の違い。同じ値なのに、Entraでは「オブジェクト ID」、AWS の承認ルールでは「グループ ID」と表記が変わります。最初「グループID」と言われて Entra 側のどれを指すのか迷いましたが、指しているのは同じ Entra グループの GUID です。文言に惑わされず「GUID を渡す」と覚えればOK。
手順1:Entra 側 — memberOf クレームを出す
エンタープライズアプリ(AWS Client VPN)→ シングルサインオン → 属性とクレーム → グループ要求を追加。
- 返すグループ:セキュリティ グループ
- ソース属性:グループ ID(= Object ID/GUID を値にする)
- 詳細オプション →「グループ要求の名前をカスタマイズする」→ 名前を
memberOf
⚠ 名前は厳密に
memberOf(大文字小文字も)。デフォルトの長いURI名(`http://schemas.microsoft.com/ws/2008/06/identity/claims/groups`)のままだと、値は出ていても AWS 側が読めずマッチしません。
グループを作る場所に注意(超重要)
グループは「SAMLアプリ(=トークンを発行するテナント)と同じテナント」に作ってください。 別テナントのグループはGUIDが正しくても memberOf に載らないです。
手順2:AWS 側 — 承認ルールに GUID を入れる
Entra で作った agency-a グループの Object ID を控えて、それを承認ルールの グループID に入れます。
マネジメントコンソールから:エンドポイント →「承認ルール」→「認証ルールを追加」→ 送信先 10.1.0.0/16 →「特定のアクセスグループのユーザーに許可」→ アクセスグループID に GUID を入力。
【重要】承認ルールは「デフォルト拒否」
Client VPN の承認ルールは「デフォルト拒否のホワイトリスト方式」 です(default-allow ではなく default-deny)。
- 許可したいグループ → 承認ルールを1本追加する
- 拒否したいグループ → 何もしない(許可ルールを書かなければ、デフォルトで拒否される)
- 明示的な「拒否ルール」という機能は無い。追加できるのは許可ルールだけで、拒否は「許可が無いこと」で成立する
agency-a (GUID-A) → 10.1.0.0/16 を許可 : ルール追加 agency-b (GUID-B) → 行かせたくない : ルールを足さない(=デフォルト拒否) どのグループにも許可ルールが無い宛先 : 誰も通れない
検証:メンバーなら到達、外せば拒否
接続後の疎通確認は TCP で今回は簡単に検証しました。
curl -v --max-time 5 http://10.1.0.10
読み方:Connection refused = EC2まで届いて拒否された=到達の証拠(Webサーバー未起動でも出る)。timeout = そもそも届いていない(経路 or 承認で落ちている)。
agency-a グループのメンバーシップを切り替えて検証した結果:
グループ agency-a の状態 |
curl の結果 | 意味 |
|---|---|---|
| メンバー1(自分が所属) | Connection refused(約30ms) |
到達=許可成立 |
| メンバー0(自分を外す+再接続) | timeout |
拒否成立 |
グループから外したら、切断→再接続(再認証) で新しいアサーションを発行させるのを忘れずに。古いセッションのままだと反映されません。
「メンバーなら到達、外せば拒否」が両方向で確認でき、Entra グループ単位のアクセス制御がエンドツーエンドで動作することを実証できました。
まとめ
今回、memberOf が載らない原因に気づくまで設定を何周もしてめちゃくちゃ苦戦しました……(笑)。同じ構成でハマっている人の助けになれば幸いなので、ポイントを最後にサクッとまとめておきます!
- Client VPN の承認ルールの
グループIDに Entra グループの Object ID(GUID) を入れるだけで、グループ単位のアクセス制御ができる。 - Entra 側は
memberOf(名前は厳密)/セキュリティグループ/ソース=グループID。GUID が3か所(Entra グループ Object ID = memberOf = グループID)で一致するのがキモ。 - 承認ルールは デフォルト拒否。許可したいグループだけ足し、拒否は「足さない」。
- グループは SAMLアプリと同じテナント に作ること。