ノートPC は接続済みと表示していた。AWS Client VPN クライアントは緑の状態を示し、エンドポイントはセッションを記録し、それでも私はプライベートサブネット内の EC2 インスタンスで MongoDB のポートを開けなかった。目に見えて壊れているものは何もなかった。証明書は有効で、プロファイルはコンソールから取得し、トンネルは通信を運んでいた。誤りは私の側にあった。接続済みを認可済みと読んでいたからだ。

ログインが答えるのはただ一つの問いだけです。この ID はセッションを確立してよいか。その先のすべては、あなたの証明書を決して見ない別々のルールが決めます。認証が通ったセッションと、届いたパケットは、二つの別の成功です。その間の隙間こそ、VPN は上がっているのにホストに届かないという報告の大半が住む場所です。

以下の確認は、私がいま使っている順序です。

接続済みのセッションが実際に証明するもの

ハンドシェイクの間、エンドポイントはクライアント証明書を自身のトラストストアに対して検証するか、SAML が設定されていればブラウザを ID プロバイダーへリダイレクトします。それが通れば、クライアントはエンドポイントのクライアント CIDR からアドレスを受け取り、コンソールは有効な接続を表示します。そのやり取りのどの時点でも、10.0.4.17 の 27017 番ポートに届くかどうかは誰も尋ねません。

宛先への許可は、エンドポイント上の別のオブジェクトです。すべてのフローは認可ルールと照合され、各ルールは宛先 CIDR と、そのルールが与えられるグループを結びつけます。どのルールも宛先を覆っていない場合、エンドポイントはセッションを受け入れ、通信を捨てます。バナーも、再接続のループも、拒否されたと告げるクライアント UI の表示もありません。

認可ルールは VPC ではなくリソースと比べる

ルールを一覧し、インスタンスが実際に持つアドレスの隣に置きます。

aws ec2 describe-client-vpn-authorization-rules \
  --client-vpn-endpoint-id cvpn-endpoint-0a1b2c3d4e5f6a7b8

出力にはルールごとに宛先 CIDR とグループ ID が表示されます。そこには二つの失敗が隠れています。ルールが 10.0.0.0/16 を認可しているのに、インスタンスが 10.1.0.0/16 のピア接続された VPC にある場合。ピアリングルートは存在しますが、二つ目の範囲に対する認可ルールを誰も書いていません。あるいはルールが、ユーザーがディレクトリでは所属しているものの、エンドポイントが評価するグループには属していないディレクトリグループを指している場合です。

宛先を覆う最も狭いルールを書き、二つ目の CIDR が本当に必要なときだけ二つ目を足します。クイックスタート由来の広い 0.0.0.0/0 ルールは動くので、そのまま本番まで生き残り、モデルを隠してしまいます。

パケットが動くかどうかを決めるのは二つのルートテーブル

エンドポイントルートは、宛先 CIDR をエンドポイントが関連付けられているサブネットの一つにマッピングします。それは経路の半分です。サブネット自身のルートテーブルが、パケットをどう転送するかを依然として知っている必要があり、宛先がピア接続、トランジットゲートウェイ、またはサイト間トンネル経由で届くオンプレミス範囲の向こう側にある瞬間に効いてきます。

aws ec2 describe-client-vpn-routes \
  --client-vpn-endpoint-id cvpn-endpoint-0a1b2c3d4e5f6a7b8

宛先がリソースの CIDR と一致し、ターゲットが関連付けられたサブネットであるルートを探します。ルートがなければ追加します。あってホストがなお沈黙しているなら、隙間は VPC 側にあるので、関連付けサブネットのルートテーブルと比べます。

クライアント側の一つの設定が、トンネルを壊さずにルーティングを壊します。route-nopull を含むプロファイルです。この指示は OpenVPN ベースのクライアントに、サーバーが押し出すルートを無視するよう伝えるので、インターフェースはエンドポイント自身のアドレスだけで上がり、宛先にはルートがまったくなくなります。Linux では ip route show、macOS では netstat -rn で tun インターフェース経由のルートがあるか確認します。なければ、エクスポートしたプロファイルから route-nopull を外して再接続し、押し出されたルートを適用させます。

セキュリティグループはクライアントのアドレスを見ていない

これが私が半日ほど間違えていた部分です。サブネットに関連付けられた IPv4 エンドポイントでは、サービスがクライアントの送信元アドレスを、そのサブネット内のエンドポイントのネットワークインターフェースのアドレスに変換します。インスタンスに届くパケットは、クライアントが表示していたアドレスを運びません。そのため、クライアントプール、たとえば 172.16.0.0/22 を許可するセキュリティグループのルールは何にも一致しません。

ポートはクライアント CIDR からではなく、関連付けのインターフェースに付いたエンドポイントのセキュリティグループから許可します。グループを参照するほうが、アドレスを直書きするより長持ちします。インターフェースのアドレスは自分で制御できるものではないからです。ネットワーク ACL はステートレスなので、カスタムのエントリがある場所では、受信と送信の両方向でフローを許可する必要があります。

インスタンスのプライベートアドレスにも接続します。パブリック IP はパケットを VPC の外へ出し、別のルールを持つ別の経路で戻すので、パブリックアドレスでの試験はプライベート経路について何も語りません。

最初の失敗で止まる切り分けの順序

これを順に進め、どこかで失敗したらそこで止めます。各手順はすべての層ではなく、一つの層を除外します。

  1. セッションがどの ID を使い、エンドポイントがどのグループを評価するかを確認します。一つのトラストストアの証明書は、ディレクトリからの SAML アサーションと同じ主体ではありません。
  2. 認可ルールがそのグループに対してリソースの CIDR を覆っていることを確認します。
  3. エンドポイントルートが存在し、関連付けられたサブネットを指していること、次にサブネットのルートテーブルが先へ転送できることを確認します。
  4. セキュリティグループがクライアントプールからではなく、エンドポイントのインターフェースからポートを許可していることを確認します。
  5. アプリケーションの問題とクライアント経路の問題を切り分けるため、VPC の内側から、同じサブネットの別インスタンスで SSM セッションを使って試します。

手順が決め手にならないとき、私が頼るのがこの表です。

SymptomLayer to inspect
Client shows connected, host times out, no byte growth on the interfaceAuthorization rule, then security group
Ping fails but a TCP connect to the port succeedsICMP is blocked; not an access failure
Tunnel reconnects every few minutesTwo machines sharing one client certificate
Works on one laptop, fails on anotherLocal profile has route-nopull or stale pushed routes

アカウントの二つの詳細が、権限の失敗に見える症状を生みます。利用者ごとに別のクライアント証明書が必要です。一つを二台で共有するとセッションが互いを押し出し、結果は拒否ではなく再接続のループになります。新しいエンドポイントを作ると、コンソールは DNS 名を表示しますが、一部のクライアント版はエンドポイントのページに表示されるランダムな接頭辞を付けた場合しか受け付けません。再構成せず、完全な名前をコピーしてください。

五番目の手順で行き詰まったら、何かを変える前にエンドポイントの接続ログを取り、認可のエントリを読みます。ログは確認された宛先と、一致したかしなかったルールを記し、四層の問いを一つの答えに畳みます。

関連記事