All Articles

Azure VPN Gateway による Site-to-Site VPN 接続とクエリフォワーディング

はじめに

AI/ML を担当するクラウドソリューションアーキテクトにとって一番大事な技術は何でしょうか。考えるまでもありませんね。ネットワークです。ネットワークの前では深層学習の知見も Transformer の構造の話も  OpenMPI を使ってマルチ GPU で分散深層学習するための様々な知識も些末なことです。なぜネットワークがここまで重要かと言うと、組織的にクラウドサービスを使う場合には AAD 改め Microsoft Entra ID による認証・認可を含めた多層防御を構成する一層としての閉域化が重要だからです。閉域化とはすなわち、特定のネットワークセグメントに PaaS その他クラウドサービスのアクセス経路を限定することでセキュリティのうち特に秘匿性を高めるものです。(常にそのルールが合理的で意味あるものかどうかは考えて欲しいですが)閉域化が基本的なルールとして定められていて、閉域化のハードルを越えなければクラウドサービスを使えないケースは経験的に非常に多いです。というわけで、ずっとネットワークしばき回して閉域化検証してるわけです。

今回はその閉域化検証を楽にするため、Azure 上に構築したハブ VNet (ハブとは何ぞやという話は後でします) に対して自宅のメインルーターである opnsense 搭載の自作ルーターから Site-to-Site VPN で通信経路を確保し、さらに閉域化した Azure サービス群に対してローカルネットワーク上のマシンから接続するために DNS の設定(なぜ DNS に手を加えるのかも後でします)に手を加えます。

前提の話

Private Endpoint と VNet Service Endpoint

PaaS へのアクセス経路を Azure の仮想ネットワーク (VNet) のみに限定するためによく使われる仕組みが Private Endpoint と VNet Service Endpoint です。

Private Endpoint は PaaS のエンドポイントを VNet 内に設けるイメージです。PaaS の正面に構えているロードバランサーと VNet 内に配置されプライベート IP を持つエンドポイント (Private Endpoint) をリンクすることで、PaaS への API コールをパブリック IP ではなくプライベート IP で受けられます。技術的に正確かはちょっと分かりませんが誤解を恐れずに言えば、NAT に近い仕組みと言えそうです。

Private Link

Private Endpoint が作成されると、Private Endpoint を紐づけた PaaS リソースの FQDN に対して CNAME が設定されます。

例えば、Azure Container Registry のサービスインスタンスを closedmlcr という名称で作ったとします。このとき FQDN は closedmlcr.azurecr.io となります。このサービスインスタンスに対し Private Endpoint を作成すると、closedmlcr.azurecr.io に対し CNAME で closedmlcr.privatelink.azurecr.io がセットされ、closedmlcr.privatelink.azurecr.io を解決すると Private Endpoint のプライベート IP が解決されるようになります。すなわち、サービスインスタンスの FQDN に対し最終的にプライベート IP が解決されるような挙動になり、その結果としてアクセス先がプライベート IP になるということです。

DNS resolve

closedmlcr.privatelink.azurecr.io を正常に名前解決するには、その名前空間に対応した Azure DNS Private Zone が必要です。通常 Private Endpoint を作成する際に自動で作成されます。VNet 内のリソースは Azure provided DNS という Azure 内からしかアクセスできない特殊な DNS (168.63.129.16) で名前解決を行いますが、もし DNS Private Zone がその VNet に紐づけられていると Private Endpoint のプライベート IP が正常に解決されるようになります。

さて、そうなるとオンプレミス環境から VPN や専用線越しに Private Endpoint 経由で PaaS を使うには、Azure 内からしかアクセスできないはずの Azure provided DNS で名前解決をする必要があるということになります。これを実現するために、オンプレミス DNS から Azure provided DNS に対してクエリを転送する際に中継点となる Azure DNS Private Resolver や Azure Firewall の DNS Proxy 機能があります。

というわけで、Private Endpoint は DNS と深い関係があります。この辺りの事情により、後述のクエリフォワーディングが必要となります。

対して VNet Service Endpoint は PaaS のパブリックなエンドポイントに対し VNet からルーティングを行うイメージです。VNet のサブネットに対し PaaS 側で通信許可する形で設定し、サブネット内のプライベート IP からサービスに対して通信を行うことを可能にする機能です。

VNet Service Endpoint

この方式の場合は普通にパブリックのエンドポイントを使ってアクセスしていますが、パブリックな経路は使用せず直接 VNet から Azure のバックボーン経由で PaaS に対しアクセスをかけています。

ただしこの方式の場合、オンプレミスからのアクセスを行いたければ何らかの Proxy を設ける必要が生じます。Azure provided DNS と似た事情になりますが、VNet Service Endpoint はあくまでも特定 VNet から PaaS へのアクセス許可として設定しているため、オンプレミスからの通信は直接 PaaS に向かうことができず遮断されてしまいます。PaaS がアクセス許可しているサブネット内にきちんと実体 (プライベート IP) が存在するプロキシサーバーを経由してアクセスすることになります。

よって、オンプレミスからのアクセス要件がある場合には Private Endpoint、Azure 内でアクセスが完結する場合にはどちらでも良いということになるかと思います。Private Endpoint はトラフィック量に応じた課金がある一方 VNet Service Endpoint の場合は特段の追加料金はかからないという性質があったり、SQL Database などでは Private Endpoint の場合はプロキシモードでの通信となる一方 VNet Service Endpoint の方はリダイレクトモードが使えるためパフォーマンスが微妙に後者の方が高いという事情があるため、Azure 内でアクセスが完結する場合には積極的に VNet Service Endpoint を選択することもあります。

余談ですが、SQL Database でリダイレクトモード目当てに VNet Service Endpoint を採用するべきかというと必ずしもそう言い難い部分があり、高可用性要件がある場合には自動フェールオーバーグループという DNS を利用したリージョン分散の仕組みを使うことができる Private Endpoint を採用することもしばしばです。

ハブ&スポーク構成

ハブ&スポーク構成はネットワーク実装のアーキテクチャです。

要諦はオンプレミス側ネットワークとの接点となる VPN や専用線のゲートウェイやパブリックネットワークへの出口となる Azure Firewall などの外部との接点となるものを集めたハブ VNet と、個別のプロジェクトやサービスに関わるリソースを内部に持つ複数のスポーク VNet に分かれていることです。ハブ VNet とスポーク VNet は VNet peering によって接続されており、通信が可能です。

Hub & Spoke

各スポーク VNet からの通信はユーザー定義ルート (UDR) 等によって Azure Firewall に強制的に向けられ、Azure Firewall では各種スポーク VNet ごとに外向き通信の許可設定を行います。

Private Endpoint を使う場合はハブ VNet 内に Azure DNS Private Resolver の Inbound エンドポイントを建てるか、Azure Firewall の DNS Proxy を使う必要があります。オンプレミスの DNS は VPN か専用線を経由してハブ VNet 内の DNS Proxy に対し閉域化された PaaS の名前解決を委任し、DNS Proxy は Azure provided DNS を使って名前解決を行い、プライベート IP を返却します。オンプレミスリソースは返ってきたプライベート IP に対し通信を行い、VPN または専用線からハブ VNet を出て、さらに VNet peering によって繋がった各種スポーク VNet 内の Private Endpoint と通信します。

この構成により、Azure Firewall やオンプレミスとの接点となるゲートウェイや DNS Proxy の類を集中管理しつつ、各スポーク VNet で共有することが可能になります。そこそこ規模が大きい組織で閉域で PaaS を使いたい場合には極めて有効なアーキテクチャで、1 回ハブ&スポーク構成でネットワークインフラを構成してしまえば急激に PaaS 活用が進むこともしばしばです。

Azure Firewall はそもそも共有前提のスケーラビリティに優れた高額サービスですので、共有せずに各スポーク VNet に作っているとお金がいくらあっても足りないという事情もあったりします。

VPN 接続

IPsec VPN による Site-to-Site 接続の確立

やや古めですが、opnsense 側に詳細なドキュメントがありました。原則こちらに沿って設定します。

本ブログでは設定項目に不足があったり Azure 側アップデートにより項目に迷う部分についてのみ記述しておきます。

事前準備

事前準備としてまず VNet を作成します。ハブ VNet には今後ネットワーク接点となる各種リソースを展開することになり、サービスごとに推奨(必要)アドレス空間サイズとサブネット名が決められています。拡張性を考えるとアドレス空間はやや広めがおすすめです。

サービス名 目的 必要なアドレス空間 サブネット名
Azure VPN Gateway VPN 接続 /27 以上 GatewaySubnet
Azure Bastion SSH や RDP の中継 /26 以上 AzureBastionSubnet
Azure Firewall ファイアウォール、DNS Proxy /26 以上 AzureFirewallSubnet

続いて Azure VPN Gateway リソースを作成しておきます。名前や地域 (リージョン) などは割愛しています。

パラメーター
ゲートウェイの種類 VPN
VPN の種類 ルートベース
SKU Basic
世代 Generation1
仮想ネットワーク <ハブに使用する VNet>
ゲートウェイ サブネットのアドレス範囲 (GatewaySubnet を作成済みの場合は表示されない) GatewaySubnet に渡すアドレス空間
パブリック IP アドレス 新規作成
アクティブ/アクティブ モードの有効化 無効
BGP の構成 無効

個人で使うもののため、コストを抑制するために Basic SKU を選択しています。Basic SKU を使う場合は Basic SKU のパブリック IP を作ることになりますが、Basic SKU のパブリック IP は 2025 年に廃止予定です。おそらく VPN Gateway の Basic SKU も廃止されるんじゃないかと思うのですが、いまいちよく分かりません。手順自体は現行 SKU である VpnGwx 系 SKU でも基本的には同じと思いますので、当面このまま使おうと思います。

どれくらい価格差があるかというと、Basic が約 3700 円/月に対し、最も安い AZ 冗長化なしの VpnGw1 が約 19600 円/月です。実運用環境ならば Basic は使うべきではありませんが、今回は検証用ですし金銭的事情もあるので安い方にしています。

Basic SKU の IP は動的割り当てです。よほど変わらないとは思いますが、変動した場合は opnsense 側設定を書き換える必要があります。あるいは Azure DNS を利用してレコードを登録するか、あるいは VPN Gateway リソース作成前に Basic の IP を作っておいて DNS 名を割り当てておく (VPN Gateway に割り当たっている間は追加できない) かしておくと変動しても名前で追跡できるので楽ができるかもしれません。

序盤、Firewall Rules OPNsense で WAN 側にいくつかルール追加を行うよう指示がありますが、これらは opnsense の最近のバージョンでは自動追加 (プルダウンメニューで隠されている) されるので不要です。

Azure 側設定

Step3 まではドキュメント通りなので割愛します。

Step4 からやや画面が異なるので補足します。

まずローカルネットワークゲートウェイリソースの作成です。これは opnsense 側の IP 等の情報を Azure リソースとして取り扱うためのラッパーみたいなものです。Azure Portal 上部検索窓に「ローカルネットワークゲートウェイ」と打てば出てきます。

パラメーター
エンドポイント IP アドレス
IP アドレス <固定 IP>
アドレス空間 <ローカルの LAN IP 空間> + <トンネル用 IP (空間)>
BGP 設定の構成 いいえ

もし固定 IP を持っていない場合はドメインを買って cloudflare あたりで DynamicDNS で FQDN で変動 IP を追えるようにした上で、エンドポイントを FQDN にしましょう。

Step5 では Connection リソースを作成します。

パラメーター
接続の種類 サイト対サイト (IPsec)
仮想ネットワークゲートウェイ <事前準備で作った VPN GW を選択>
ローカルネットワークゲートウェイ <ついさっき作ったローカルネットワークゲートウェイ>
共有キー(PSK) <生成したキー>
Azure プライベート IP アドレスを使用する チェック無し
BGP を有効にする チェック無し
カスタム BGP アドレスを有効にする チェック無し
IPsec および IKE ポリシー 既定
ポリシーベースのトラフィックセレクターを使用する 無効化
DPD タイムアウト (秒) 45 (デフォルト)
接続モード Default (多分 InitiatorOnly でも良さそう)

opnsense 側設定

Step7 の静的ルート追加ですが、ハブ VNet に対するルートはもちろんのこと、スポーク VNet があればスポーク VNet に対する静的ルートも同様に作っておきましょう。

Step5 の後に Firewall Rules OPNsense で IPsec のインターフェイスから LAN net へのアクセスを許可するルールを追加しましたが、LAN インターフェイス側にもハブ VNet およびスポーク VNet に対する通信許可が必要です。

LAN rule

疎通確認はこのあと DNS 設定後にやります。

DNS 設定

前提として、opnsense 内蔵の Unbound DNS を使います。他の DNS を使う場合、Unbound DNS では「Query Forwarding」となっている機能も他の DNS であれば「条件付きフォワーディング」とか「DNS フォワーディング」とか色々別名があると思うので、適宜読み替えてください。

Private DNS Zone 紐づけ

スポーク VNet 側で PE を作ると Private DNS Zone が作られますが、この Private DNS Zone は複数 VNet に紐づけることができます。ハブ VNet についてもリンクしておくことで、この後行う DNS Proxy 設定とクエリフォワーディングと合わせてローカル側 DNS でも PE の名前を解決できるようになります。

VNet link

DNS Proxy 設定

DNS Proxy の選択肢は前述の通り Azure Firewall と Azure DNS Private Resolver ですが、今回はアウトバウンド制御で Azure Firewall を使うという事情もあり、課金抑制のため Azure Firewall を使用します。

Azure Firewall Policy からプロキシ設定を有効にします。

DNS Proxy

この設定により、Azure Firewall のプライベート IP アドレスを Azure provided DNS に名前解決を丸投げする DNS の IP として扱えるようになります。

余談ですが、実は Azure Firewall は割り当てを解除することで課金を止めることができます。財布に優しくて良いですね。ただし止めると DNS へのプロキシも機能しなくなる点には要注意です。後述のクエリフォワーディングで各サービス全体に対する名前空間について解決を委任しているため、閉域化していないリソースについても名前解決を DNS Proxy に任せようとします。そのとき Azure Firewall が停止状態だと名前解決に失敗し接続できないという状況が発生します。

Azure Firewall の割り当て停止を行う場合、クエリフォワーディングについても停止時には無効化しておくとか、あるいは常時起動を前提に DNS Private Resolver を使うなどの対策をした方が事故防止になりそうな気もします。(Azure Firewall を常時起動すると月額約 10 万円ほど、DNS Private Resolver の場合は月額 3 万円弱ほど)

クエリフォワーディング

クエリフォワーディング設定を行います。PE を作ったサービスについて、各サービスごとに指定されている FQDN の名前解決を Azure Firewall にフォワーディングします。

Query Forwarding

一応このほかに、システム → 設定 → 全般からゲートウェイを無指定の DNS として Azure Firewall を入れています。なくても良い気はする。

おわりに

閉域化したサービスについて名前解決してみます。これは DNS 周りの設定が無事完了しているかどうかに加え、DNS までの経路である VPN が正常動作しているかの確認も兼ねています。(本当は VM とか立てて ping とかでやれば良い気もする)

PS C:\workspace\shuit> nslookup closedmlcr.azurecr.io
サーバー:  opnsense.shinagawa.theitos.org
Address:  192.168.1.1

DNS request timed out.
    timeout was 2 seconds.
DNS request timed out.
    timeout was 2 seconds.
権限のない回答:
名前:    closedmlcr.privatelink.azurecr.io
Address:  10.3.1.13
Aliases:  closedmlcr.azurecr.io
PS C:\workspace\shuit> nslookup shuitclosedmlw8123566885.vault.azure.net
サーバー:  opnsense.shinagawa.theitos.org
Address:  192.168.1.1

DNS request timed out.
    timeout was 2 seconds.
DNS request timed out.
    timeout was 2 seconds.
権限のない回答:
名前:    shuitclosedmlw8123566885.privatelink.vaultcore.azure.net
Address:  10.3.1.11
Aliases:  shuitclosedmlw8123566885.vault.azure.net

できてますね。パーフェクト。

これでスポーク VNet を増やしていって、その都度 Query Forwarding 設定や静的ルート設定、LAN 側のルール設定を増やしていくことで簡単に閉域検証ができます。

Published 2023/08/14

ShuntaIto による技術ブログ