AWS Client VPN が一致するルートなしで通信を破棄する理由
エンドポイントは、ルートテーブルが宛先を覆っている場合にのみパケットを転送します。ルートを読み、for_each でプレフィックスを宣言し、戻り経路を確認します。
Client VPN はクライアントにアドレスを渡し、ハンドシェイクも完了するのに、接続はどこにも届きません。証明書やクライアントのバージョンに触れる前に、エンドポイント自身のルートテーブルを見てください。AWS Client VPN エンドポイントは、いずれかのルートが宛先プレフィックスを覆っている場合にだけパケットを転送します。何も一致しなければ、パケットはエンドポイントで破棄され、宛先ホストは SYN を一度も見ません。
これは転送の判断であり、認証の判断ではありません。トンネルが確立し、認可ルールが評価された後に起きます。だからこそ、有効な証明書と有効なグループでも死んだ接続になり得ます。
エンドポイントはルートテーブルが覆う範囲だけを転送する
各ルートは宛先 CIDR と、通信を受け取るべきサブネットの組です。エンドポイントはクライアントの宛先をその一覧と照合し、一致する項目を選び、パケットを関連付けられたサブネットへ送ります。 項目がなければネクストホップもない。
ルートが何ではないかに注意してください。VPC のルートテーブルの項目ではなく、何かを認可するものでもありません。認可ルールはグループが宛先に到達できるかを決め、ルートはエンドポイントにパケットを送る先があるかを決めます。両方が一致する必要があり、失敗の症状も異なるため、どちらが壊れているかを推測すると半日が無駄になります。
典型的な食い違いはピア接続された VPC です。ホストが 10.20.4.0/24 にいるのに、誰かが 10.20.0.0/24 へのルートを追加するため、アドレスはどの項目にも入りません。クライアント側のキャプチャには応答のない SYN が繰り返し現れ、宛先のログには何も残りません。
証明書より先にルート一覧を読む
エンドポイントのルートは独立したオブジェクトなので、VPC のテーブルを読む代わりに直接問い合わせます。
aws ec2 describe-client-vpn-routes --client-vpn-endpoint-id cvpn-endpoint-0123456789abcdef0 --query 'Routes[].{Dest:DestinationCidr,Target:TargetSubnet,Origin:Origin}'失敗しているクライアントから正確なアドレスとプレフィックス長を取り、被覆を一文字ずつ確認します。最長プレフィックス一致が適用されるため、より狭いルートが別の場所を指していれば、広いルートでは救えません。
for_each で宛先ごとに個別のリソース
手書きのルートブロックは、誰かがコンソールでプレフィックスを追加した瞬間にずれます。宛先をマップに保ち、キーごとにリソースをひとつ作れば、追加も削除も分離されたままです。
variable "client_vpn_routes" {
type = map(string)
default = {
"10.20.0.0/16" = "subnet-0aa11bb22cc33dd44"
"172.31.0.0/20" = "subnet-0aa11bb22cc33dd44"
}
}
resource "aws_ec2_client_vpn_route" "this" {
for_each = var.client_vpn_routes
client_vpn_endpoint_id = var.client_vpn_endpoint_id
destination_cidr_block = each.key
target_vpc_subnet_id = each.value
depends_on = [aws_ec2_client_vpn_network_association.this]
}ネットワーク関連付けへの依存を保ってください。ルートには関連付けたサブネットが必要なので、両方を作る apply は関連付けを先に並べる必要があり、その参照がないと Terraform はルートを先に試すことができます。次に plan を読みます。ルート追加の数は、追加したプレフィックスと一致するはずです。plan がエンドポイントも置き換えるなら、エンドポイントのリソースが誤って書かれており、適用すると接続中の全クライアントが切断されます。
自分が作っていないエンドポイントを引き継ぐ
多くのチームはコンソールで作られたエンドポイントを引き継ぐため、ルートのコードが存在しません。ルートを Terraform に入れるためだけに 別のエンドポイントを立てないでください。
代わりに import するか、id を変数として渡し、引き継いだオブジェクトは管理外のままにします。import にはリソース種別ごとにプロバイダが定める識別子の形式が必要で、それはエンドポイント、宛先プレフィックス、対象サブネットを符号化します。記憶から組み立てるのではなくリソースのドキュメントからその文字列をコピーし、plan を実行し、新しいものを追加する前に変更がないことを確認してください。
パケットが進む順にたどる
ルートが存在しても接続が失敗するときは、上位層で推測せず、経路を順にたどってください。
- ルート: プレフィックスが宛先を覆い、エンドポイントに関連付けられたサブネットを指している。
- 認可: 認可ルールが、その宛先をユーザーのグループに許可している。ルートだけでは決してアクセスは与えられない。
- 戻り経路: 宛先サブネットのルートテーブルが応答をエンドポイントへ戻し、セキュリティグループがクライアントのアドレスプールからのポートを許可している。
- アプリケーション: クライアントから実際の TCP 接続を開き、宛先のログにプールからの想定どおりの送信元アドレスが出ることを確認する。
apply ごとにプレフィックスをひとつ変え、その後でルート一覧を読み直し、宛先、認可するルール、戻り経路をまとめて記録します。拒否された接続を次に調べる人は、証明書の理論ではなく、その棚卸しから始めるべきです。