インターネット向けのルート(0.0.0.0/0)を持つ AWS Client VPN エンドポイントでスプリットトンネルを有効化してみた

0.0.0.0/0 もクライアントデバイスのルートテーブルにプッシュされると困るのでは?と思っていたのですがそこはうまいこと回避されるようでした。

コンバンハ、千葉(幸)です。

突然ですが、問題です。

以下のような構成の AWS Client VPN エンドポイントがあったとします。

  • パブリックサブネットをターゲットネットワークとして関連づけ済み
  • Client VPN ルートテーブルで 0.0.0.0/0 向けのルートを追加済み
  • 承認済みの宛先ネットワークとして全てのユーザーを対象に 0.0.0.0/0 を定義済み
  • スプリットトンネルを有効化済み

ここで、Client VPNエンドポイントに接続したクライアントがインターネット宛の通信を行いたい場合、その経路は以下イメージの①②いずれとなるでしょうか。

  • ①VPN トンネルを経由せず直接アクセス
  • ②ターゲットネットワークからインターネットゲートウェイ経由でアクセス

はい。

はい。

正解は、①です。

私は「スプリットトンネルを有効化したら、インターネット向けの通信って VPN トンネル経由しないんでしょ?」という雑な理解をしていました。

概ねその通りなのですが、もう少し深掘りして見えてきたものがあるので、今回はそれをとりあげます。

目次

先にまとめ

  • スプリットトンネルが無効の場合、クライアントからのすべてのトラフィックは AWS Client VPN トンネルを経由してルーティングされる
  • スプリットトンネルが有効の場合、「特定の宛先」に対するトラフィックのみ AWS Client VPN トンネル経由でルーティングされる
    • 「特定の宛先」とは、AWS Client VPN エンドポイントルートテーブルで定義されている宛先を指す
    • エンドポイントルートテーブルのルートがクライアントデバイスのルートテーブルにプッシュされることで実現する
    • ただし 0.0.0.0/0 向けのルートはプッシュされない

やってみた

今回は以下のような構成で試してみます。

  • クライアントデバイスが所属する CIDR :192.168.0.0/24
  • クライアント CIDR:172.31.0.0/16
  • VPC CIDR:192.168.0.0/16

Client VPN ルートテーブルには以下を登録します。

  • 192.168.0.0/16(デフォルトで登録される VPC CIDR)
  • 0.0.0.0/0
  • 8.8.8.8/32

8.8.8.8/32を含めているのは、デバイスのルートテーブルにルートがプッシュされる挙動が確認しやすいようにという意図です。

以下のパターンにおいて、クライアントデバイスのルートテーブルと、インターネットに接続する際の経路を確認します。

  1. Client VPN に接続しない場合
  2. スプリットトンネル無効の場合
  3. スプリットトンネル有効の場合

インターネットへの接続経路のチェックには、checkip.amazonaws.comを使用します。接続に使用したグローバル IP を返却してくれるため、それによって判別します。

今回の環境で使用されるグローバル IP は以下の通りです。

  • クライアントデバイス環境
    • 202.xx.xx.xx
  • ターゲットネットワーク ENI のパブリック IP
    • 3.xx.xx.xx
    • 18.xx.xx.xx

なお、クライアントに関しては以下の条件で試しています。

  • クライアントデバイス:macOS Catalina 10.15.7
  • VPN クライアント:AWS VPN Client 1.2.0

1. Client VPN に接続しない場合

素の状態でのクライアントデバイスのルートテーブルが以下です。

% netstat -rn
Routing tables

Internet:
Destination        Gateway            Flags        Netif Expire
default            192.168.0.1        UGSc           en0
127                127.0.0.1          UCS            lo0
127.0.0.1          127.0.0.1          UH             lo0
169.254            link#6             UCS            en0      !
192.168.0          link#6             UCS            en0      !
192.168.0.1/32     link#6             UCS            en0      !
192.168.0.1        90:f3:5:ee:a2:bb   UHLWIir        en0   1169
192.168.0.14/32    link#6             UCS            en0      !
224.0.0/4          link#6             UmCS           en0      !
224.0.0.251        1:0:5e:0:0:fb      UHmLWI         en0
239.255.255.250    1:0:5e:7f:ff:fa    UHmLWI         en0
255.255.255.255/32 link#6             UCS            en0      !

使用されるグローバル IP アドレスを確認すると、手元の環境で使用しているものが返却されました。

% curl checkip.amazonaws.com
202.xx.xx.xx

当然と言えば当然ですね。

2. スプリットトンネル無効の場合

スプリットトンネルを無効にした状態の Client VPN に接続し、試します。

ルートテーブルはこのようになりました。

% netstat -rn
Routing tables

Internet:
Destination        Gateway            Flags        Netif Expire
0/1                172.31.0.33        UGSc         utun2
default            192.168.0.1        UGSc           en0
54.178.6.141/32    192.168.0.1        UGSc           en0
127                127.0.0.1          UCS            lo0
127.0.0.1          127.0.0.1          UH             lo0
128.0/1            172.31.0.33        UGSc         utun2
169.254            link#6             UCS            en0      !
172.31.0.32/27     172.31.0.34        UGSc         utun2
172.31.0.34        172.31.0.34        UH           utun2
192.168.0          link#6             UCS            en0      !
192.168.0.1/32     link#6             UCS            en0      !
192.168.0.1        90:f3:5:ee:a2:bb   UHLWIir        en0   1129
192.168.0.14/32    link#6             UCS            en0      !
224.0.0/4          link#6             UmCS           en0      !
224.0.0.251        1:0:5e:0:0:fb      UHmLWI         en0
239.255.255.250    1:0:5e:7f:ff:fa    UHmLWI         en0
255.255.255.255/32 link#6             UCS            en0      !

クライアントデバイスルートテーブルの変更点

デフォルトの状態から変わった部分を確認していきます。

0/1 と 128.0/1

これはそれぞれ以下を表します。

  • 0.0.0.0/1(0.0.0.1 ~ 127.255.255.254)
  • 128.0.0.0/1(128.0.0.1 ~ 255.255.255.254)

ゲートウェイを見るとどちらも Client VPN を向いていることが分かります。

両者を合算して 0.0.0.0/0 のレンジをカバーすることになるのですが、なぜ異なるルートとして登録されているかが気になりました。

どうやら、デフォルトルート(0.0.0.0/0)よりロンゲストマッチ(最長プレフィックス一致)するように、このような方式になっているようです。

ただ、この方式がすべての環境で適用されるかは分かりません。使用しているデバイスや VPN クライアントによって変わる可能性もある旨ご留意ください。

ドキュメントでは「 0.0.0.0/0 で上書きする」と記載があります。

AWS Client VPN エンドポイントで分割トンネルを有効にすると、VPN が確立された場合に、AWS Client VPN ルートテーブル内のすべてのルートがクライアントルートテーブルに追加されます。この操作は、デフォルトのエンドポイントオペレーションとは異なります。デフォルトの AWS Client VPN エンドポイントオペレーションでは、クライアントルートテーブルがエントリ 0.0.0.0/0 で上書きされ、すべてのトラフィックが VPN 経由でルーティングされます。

Client VPN エンドポイント グローバル IP

ここでの54.178.6.141/32はエンドポイントDNS名を名前解決した際に引ける IP(の一つ)です。ここに向けた通信は VPN トンネル向けにルーティングされては困るため、クライアントデバイスのデフォルトゲートウェイを向くように設定されています。

Client CIDR

この例においてクライアントに割り当てられている IP は172.31.0.34です。(クライアント CIDR のプールの中から接続時にランダムで割り当てられます。)

そこをゲートウェイとするルートが定義されています。また、第4オクテットが1番違いの172.31.0.33が、デフォルトルートのゲートウェイとして設定されています。

こういったルートの追加により、Client VPN に接続した際のルーティングが行われています。

インターネットへの疎通確認

確認を行うと、先ほど確認したものとは別のグローバル IP が返却されます。

% curl checkip.amazonaws.com
18.xxx.xx.xx

これは、冒頭で確認した通りターゲットネットワークに生成された ENI が持つパブリック IP アドレスです。

EC2 サービス画面のネットワークインタフェースの一覧画面から確認できます。

ENI は 2つあり、 Client VPN 接続時にどちらの ENI に割り振られるかが決定されます。 Client VPN 接続後の通信はこの ENI を経由して行われるため、この ENIに設定されている SecuriryGroup や NetworkACL 、ルートテーブルなどの設定に影響を受けます。

3. スプリットトンネル有効の場合

Client VPN エンドポイントの設定でスプリットトンネルを有効化し、再度クライアントから接続し直します。

クライアントデバイスのルートテーブルはこのようになりました。

% netstat -rn
Routing tables

Internet:
Destination        Gateway            Flags        Netif Expire
default            192.168.0.1        UGSc           en0
8.8.8.8/32         172.31.0.129       UGSc         utun2
127                127.0.0.1          UCS            lo0
127.0.0.1          127.0.0.1          UH             lo0
169.254            link#6             UCS            en0      !
172.31.0.128/27    172.31.0.130       UGSc         utun2
172.31.0.130       172.31.0.130       UH           utun2
192.168.0          link#6             UCS            en0      !
192.168.0/16       172.31.0.129       UGSc         utun2
192.168.0.1/32     link#6             UCS            en0      !
192.168.0.1        90:f3:5:ee:a2:bb   UHLWIir        en0   1152
192.168.0.14/32    link#6             UCS            en0      !
224.0.0/4          link#6             UmCS           en0      !
224.0.0.251        1:0:5e:0:0:fb      UHmLWI         en0
239.255.255.250    1:0:5e:7f:ff:fa    UHmLWI         en0
255.255.255.255/32 link#6             UCS            en0      !

クライアントデバイスルートテーブルの変更点

先ほどと同じように確認していきます。

Client VPN ルートテーブルからプッシュされたもの

おさらいになりますが、今回は Client VPN ルートテーブルに以下 3 つのルートを登録しています。

  • 192.168.0.0/16(デフォルトで登録される VPC CIDR)
  • 0.0.0.0/0
  • 8.8.8.8/32
% aws ec2 describe-client-vpn-routes --client-vpn-endpoint-id cvpn-endpoint-xxxxxxxxxx --output yaml
Routes:
- ClientVpnEndpointId: cvpn-endpoint-0ba622xxxxxxxxxx
  Description: Default Route
  DestinationCidr: 192.168.0.0/16
  Origin: associate
  Status:
    Code: active
  TargetSubnet: subnet-0caa45223899b4b73
  Type: Nat
- ClientVpnEndpointId: cvpn-endpoint-0ba622xxxxxxxxxx
  DestinationCidr: 0.0.0.0/0
  Origin: add-route
  Status:
    Code: active
  TargetSubnet: subnet-0caa45223899b4b73
  Type: Nat
- ClientVpnEndpointId: cvpn-endpoint-0ba622xxxxxxxxxx
  DestinationCidr: 8.8.8.8/32
  Origin: add-route
  Status:
    Code: active
  TargetSubnet: subnet-0caa45223899b4b73
  Type: Nat

そのうち、0.0.0.0/0 以外の 2つのルートがプッシュされていることが分かります。

スプリットトンネル無効の場合と異なり、0/1 と 128.0/1 によるデフォルトルートの上書きや、エンドポイントのグローバル IP 向けのルート追加は行われていません。

デフォルトゲートウェイはクライアントデバイスが持っていたもののままです。

つまり、(8.8.8.8 以外の)インターネット宛の通信の経路は Client VPN を経由しません。この挙動は今回試してみて初めて知りました。

Client CIDR

こちらは先ほどと同様に追加されています。今回クライアントに割り当てられた IP は172.31.0.130です。

Client VPN ルートテーブルからプッシュされた宛先のゲートウェイは、1番違いの172.31.0.129がターゲットとなっています。

インターネットへの疎通確認

デフォルトルートに変更が加わらないことから、Client VPN に接続しない場合と同様のルートでインターネットに疎通することになります。

% curl checkip.amazonaws.com
202.xx.xx.xx

ケース 1. での確認結果と同じです。

Traceroute による確認

今回の例ではクライアント VPN ルートテーブルから8.8.8.8/32がプッシュされているため、クライアントからそこに向けた通信は経路が異なる、という点を確認します。

まずはクライアントデバイスのデフォルトルートを経由する際の結果のイメージです。

% traceroute 1.1.1.1
traceroute to 1.1.1.1 (1.1.1.1), 64 hops max, 52 byte packets
 1  192.168.0.1 (192.168.0.1)  1.559 ms  1.205 ms  1.190 ms
 2  * * *
 3  10.202.xxx.xxx (10.202.xxx.xxx)  39.350 ms  17.774 ms  14.830 ms
(……中略……)
 9  one.one.one.one (1.1.1.1)  14.070 ms  65.633 ms *

続いて 8.8.8.8 向けの通信です。経路が異なることが分かります。

% traceroute 8.8.8.8
traceroute to 8.8.8.8 (8.8.8.8), 64 hops max, 52 byte packets
 1  * * *
 2  ec2-54-150-128-23.ap-northeast-1.compute.amazonaws.com (54.150.128.23)  28.393 ms
    ec2-54-150-128-25.ap-northeast-1.compute.amazonaws.com (54.150.128.25)  33.160 ms
    ec2-54-150-128-23.ap-northeast-1.compute.amazonaws.com (54.150.128.23)  27.409 ms
 3  100.65.xx.xxx (100.65.xx.xxx)  25.229 ms
(……中略……)
10  dns.google (8.8.8.8)  18.695 ms  19.311 ms  25.373 ms

(なお、これはターゲットネットワークの ENI を経由して通信を行っているため、そこにアタッチされるSecuriryGroupのアウトバウンドルールの開放などの考慮が必要です。)

0.0.0.0/0 という宛先でなければ、インターネット上のグローバル IP レンジに向けたルートをプッシュできることが確認できました。

終わりに

AWS Client VPN でスプリットトンネルを有効化しても、Client VPN ルートテーブル上の 0.0.0.0/0 はクライアントデバイスにプッシュされない、というお話でした。

実はこのエントリを書き始める前は全く逆の挙動を想定していて、「インターネット向けのルートを使っている場合はスプリットトンネルが無効でも有効でも結局変わらない」という方向の話をしようとしていました。

AWS Client VPN エンドポイントで分割トンネルを使用する場合、VPN が確立されると、AWS Client VPN ルートテーブル内のすべてのルートがクライアントルートテーブルに追加されます。

ドキュメントを見てそういった思い込みをしていたのですが、検証をする中で間違いに気づけたので何よりです。

私の環境だけかもしれませんが、接続して試す→切断してクライアント VPN の設定変更する→再度接続し直して挙動を確認する、というのを短いスパンで繰り返したらよく分からない挙動になったので、設定変更をしたあとは少し寝かせてから試した方が手戻りがなくて良さそうという学びを得ました。

以上、千葉(幸)がお送りしました。

あわせて読みたい