Azure Subnet peering

Azure Virtual Network の VNet ピアリングにおいて、特定のサブネットのみを選んでピアリングできる機能が提供されております。
今までは Hub&Spoke のような構成で、仮想ネットワーク内のすべてのアドレス空間がオンプレ側に公報されてしまうため、Spoke VNet 内で完結する独自のサブネットを定義するといったことができませんでした。この機能を利用することで仮想ネットワーク内で独自のサブネットを定義して利用することができるようになります

https://learn.microsoft.com/ja-jp/azure/virtual-network/how-to-configure-subnet-peering

### 利用シナリオ

- オンプレと VPN/ER で接続しているケース
    - IPv4 アドレス数が潤沢ではないため、ルーティング用に必須のサブネット以外は独自サブネットを利用して IP アドレス数を節約したい。[IPv4 枯渇防止](https://learn.microsoft.com/ja-jp/azure/architecture/networking/guide/ipv4-exhaustion#method-1-nonroutable-landing-zone-spoke-virtual-networks)の対応として Non-routable な Spoke を使う手法が紹介されていますが、追加の VNet ピアリングは不要のため、構成はより容易です。
    - アドレス空間を追加するたびにネットワーク管理者と調整が必要であるため、独自サブネットを利用して自由にアドレス空間を定義したい
- セキュリティのために通信要件があるサブネットのみをピアリングしたい

### 構成図
以下のような構成にてオレンジのサブネットをピアリングします。オンプレ側とアドレス空間が重複している場合でも利用できるかを確認するため、意図的にオンプレのアドレス空間と Spoke の一部のアドレス空間を重複させています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2266293/2f19fe6d-53ba-166a-568a-935da879be47.png)

### 留意点

- "Use remote gateway" を使う場合、GatewaySubnet を含めてピアリングする必要あり
- 広報されるレンジがサブネット単位となるため UDR の設定を誤らないようにする (VNet より狭くなる)
- VNet peering と同時に利用することはできない
- サブネットの数は 400 以下にする必要あり (ローカル 200 + リモート 200)
- こちらの記事にもありますが、VNet ピアリングでピアリングするサブネットを選択できる機能であって、サブネット同士をピアリングする機能ではない

https://zenn.dev/microsoft/articles/20241006-subnetpeering#%E6%A6%82%E5%BF%B5

### 動作確認

#### ピアリングの設定
Azure CLI にてピアリング設定を行います。私の環境は VPN 接続で Hub&Spoke の構成になっているため、--use-remote-gateways true, --allow-gateway-transit true といったオプションを付けています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2266293/2f19fe6d-53ba-166a-568a-935da879be47.png)

Spoke VNet から Hub VNet へのピアリングを作成
```
az account set --subscription xxxxx-ad04-49c3-8b60-b78f6aa25829

az network vnet peering create -n "spoke1-hub" -g rg-subnetpeerint-001 \
    --vnet-name vnet-subpeering-001 \
    --remote-vnet /subscriptions/xxxxx-ad04-49c3-8b60-b78f6aa25829/resourceGroups/rg-hub-001/providers/Microsoft.Network/virtualNetworks/vnet-hub-001 \
    --allow-forwarded-traffic  \
    --allow-vnet-access \
    --peer-complete-vnets false \
    --use-remote-gateways true \
    --local-subnet-names subnet-ext-pe-001 \
    --remote-subnet-names AzureFirewallSubnet AzureBastionSubnet GatewaySubnet  
```
Hub VNet から Spoke VNet へのピアリングを作成
```
az account set --subscription xxxxxx-8e95-4093-b19c-94eba5afa598

az network vnet peering create -n "hub-spoke1" -g rg-hub-001 
    --vnet-name vnet-hub-001 \
    --remote-vnet /subscriptions/xxxxxx-8e95-4093-b19c-94eba5afa598/resourceGroups/rg-subnetpeerint-001/providers/Microsoft.Network/virtualNetworks/vnet-subpeering-001 \
    --allow-forwarded-traffic \
    --allow-vnet-access \
    --peer-complete-vnets false \
    --allow-gateway-transit true \
    --local-subnet-names AzureFirewallSubnet AzureBastionSubnet GatewaySubnet \
    --remote-subnet-names subnet-ext-pe-001 
```

#### 疑似オンプレ環境側のルーティング
オンプレ環境として作成した VNet 側にあるクライアントの有効なルートを確認してみると 192.168.x.x は Onpre-VNet の "192.168.0.0/22" のみとなっており、各 Spoke のアドレス空間で定義されている "192.168.0.0/22" が広報されていないことがわかります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2266293/2a6a9acc-0d53-f12f-ab69-d3f152659516.png)

#### Application Gateway と AppService へ接続

疑似オンプレ環境のクライアントから Application Gateway へ問題なく接続できました。
宛先にはプライベートエンドポイントの IP を指定しています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2266293/fe37f81a-cf2b-97a9-d2e5-74e04f6937f7.png)

疑似オンプレ環境のクライアントから AppService へ問題なく接続できました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2266293/4f6bf104-8904-c018-01aa-d8e1d2f67d71.png)

#### Spoke 側の VM のルーティング
Spoke-VNet1 にある linux-vm1 のルーティングを確認します。

以下の様に Spoke-VNet1 と Hub-VNet 内のピアリング済みのサブネットのアドレス空間しかありません。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2266293/914bdea6-f844-94ba-a81a-1fe71b86369f.png)

Azure ではアドレス空間が重複する場合、原則、ロンゲストマッチでルーティングが決まるはずです。
なぜ、上記のルートに疑似オンプレ環境の 192.168.0.0/22 のアドレス空間がなかったかというと以下の制限事項に合致しているためです。

https://learn.microsoft.com/ja-jp/azure/virtual-network/virtual-networks-udr-overview#how-azure-selects-a-route

:::note
仮想ネットワーク、仮想ネットワークのピアリング、または仮想ネットワーク サービス エンドポイントに関連したトラフィックに使用される**システム ルートは、BGP のルートの方が具体的であったとしても、優先されるルート**となります。
:::

試しに疑似オンプレ側のアドレス空間を以下の様に 192.168.0.0/16 に変えたところ、linux-vm1 の有効なルートに表示されるようになりました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2266293/abdd469f-29e8-d4d7-1f5f-396de6191527.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2266293/d3b26a61-84e7-1c85-57fe-cab2b038a8db.png)

ロンゲストマッチにてルーティングがきまるため、この環境では 192.168.0.0/16 の内、192.168.0.0/20 以外のアドレス空間を使わないとオンプレと linux-vm1 は通信できないことになります。

#### Spoke 間通信
linux-vm1 のサブネットに UDR を適用し、デフォルトルートが Azure Firewall 経由となるように構成します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2266293/ee6b0db3-6779-f81e-120e-eb8934eb8d8b.png)

curl コマンドで問題なく AppService にアクセス可能でした。
```
$ curl -v https://webapphiyamatest.azurewebsites.net/
* Host webapphiyamatest.azurewebsites.net:443 was resolved.
* IPv6: (none)
* IPv4: 10.100.2.5
*   Trying 10.100.2.5:443...
* Connected to webapphiyamatest-hxcdhsgwdxc7dffc.japaneast-01.azurewebsites.net (10.100.2.5) port 443
* ALPN: curl offers h2,http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
*  CAfile: /etc/ssl/certs/ca-certificates.crt
*  CApath: /etc/ssl/certs
~省略~
* using HTTP/1.x
> GET / HTTP/1.1
> Host: webapphiyamatest-hxcdhsgwdxc7dffc.japaneast-01.azurewebsites.net
> User-Agent: curl/8.5.0
> Accept: */*
> 
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
< HTTP/1.1 200 OK
< Content-Length: 3269
< Content-Type: text/html
< Date: Thu, 07 Nov 2024 08:07:35 GMT
~省略~
```

### Azure からオンプレ側への通信

#### Azure Firewall で SNAT して AppService からオンプレにアクセス

以下のような構成に変更し、AppService からオンプレにアクセスできるかを試します。
オンプレ側には 192.168.0.0/20 (192.168.0.0-192.168.15.255) と重なっていないサブネット作成し、VM (ClientVM2) を作成します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2266293/99c234cc-ad1b-dcd4-61a4-5e7608756d6a.png)

Spoke-VNet2 に配置した Azure Firewall (Standard SKU) にて常に SNAT を行うように設定をします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2266293/65d37914-f525-52a1-bd9f-e1bf59bb742a.png)

GatewaySubnet (Hub-VNet), AzureFirewallSubnet (Spoke-VNet2), subnet-int-appsrv-001 (Spoke-VNet2) の UDR を適切に構成します。**特に GatewaySubnet 用の UDR はあて先がサブネットの単位になる点は注意が必要です。**

AppService (Linux) の画面から SSH で接続します。※VNet 統合は設定済み

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2266293/db003b64-0663-4525-1856-835ce065d909.png)

問題なく接続できました。
```
root@webapphiya_20ac0bbb04:/home# curl -v http://192.168.100.4
*   Trying 192.168.100.4:80...
* Connected to 192.168.100.4 (192.168.100.4) port 80 (#0)
> GET / HTTP/1.1
> Host: 192.168.100.4
> User-Agent: curl/7.88.1
> Accept: */*
> 
< HTTP/1.1 200 OK
< Date: Fri, 08 Nov 2024 05:53:33 GMT
< Server: Apache/2.4.58 (Ubuntu)
< Last-Modified: Fri, 08 Nov 2024 04:39:23 GMT
< ETag: "a-6265f55cf47fe"
< Accept-Ranges: bytes
< Content-Length: 10
< Content-Type: text/html
< 
ClientVM2
* Connection #0 to host 192.168.100.4 left intact
```

ClientVM2 側のパケットを見てみると送信元が Spoke-VNet2 の AzureFirewallSubnet の IP アドレスになっていました。

```
05:53:33.836822 IP (tos 0x0, ttl 61, id 49810, offset 0, flags [DF], proto TCP (6), length 60)
    10.100.3.8.44808 > 192.168.100.4.80: Flags [S], cksum 0xd52d (correct), seq 1097175631, win 64240, options [mss 1226,sackOK,TS val 2374907415 ecr 0,nop,wscale 7], length 0
05:53:33.836872 IP (tos 0x0, ttl 64, id 0, offset 0, flags [DF], proto TCP (6), length 60)
    192.168.100.4.80 > 10.100.3.8.44808: Flags [S.], cksum 0x3247 (incorrect -> 0x3cb3), seq 44772019, ack 1097175632, win 65160, options [mss 1460,sackOK,TS val 1368921329 ecr 2374907415,nop,wscale 7], length 0
05:53:33.844883 IP (tos 0x0, ttl 61, id 49811, offset 0, flags [DF], proto TCP (6), length 52)
    10.100.3.8.44808 > 192.168.100.4.80: Flags [.], cksum 0x6807 (correct), seq 1, ack 1, win 502, options [nop,nop,TS val 2374907426 ecr 1368921329], length 0
05:53:33.844883 IP (tos 0x0, ttl 61, id 49812, offset 0, flags [DF], proto TCP (6), length 129)
    10.100.3.8.44808 > 192.168.100.4.80: Flags [P.], cksum 0xce9a (correct), seq 1:78, ack 1, win 502, options [nop,nop,TS val 2374907426 ecr 1368921329], length 77: HTTP, length: 77
GET / HTTP/1.1
Host: 192.168.100.4
User-Agent: curl/7.88.1
Accept: */*

05:53:33.844954 IP (tos 0x0, ttl 64, id 50513, offset 0, flags [DF], proto TCP (6), length 52)
    192.168.100.4.80 > 10.100.3.8.44808: Flags [.], cksum 0x323f (incorrect -> 0x67ab), seq 1, ack 78, win 509, options [nop,nop,TS val 1368921337 ecr 2374907426], length 0
05:53:33.845703 IP (tos 0x0, ttl 64, id 50514, offset 0, flags [DF], proto TCP (6), length 288)
    192.168.100.4.80 > 10.100.3.8.44808: Flags [P.], cksum 0x332b (incorrect -> 0x15b5), seq 1:237, ack 78, win 509, options [nop,nop,TS val 1368921338 ecr 2374907426], length 236: HTTP, length: 236
HTTP/1.1 200 OK
Date: Fri, 08 Nov 2024 05:53:33 GMT
Server: Apache/2.4.58 (Ubuntu)
Last-Modified: Fri, 08 Nov 2024 04:39:23 GMT
ETag: "a-6265f55cf47fe"
Accept-Ranges: bytes
Content-Length: 10
Content-Type: text/html

ClientVM2
```

最初は以下のような構成で Azure Firewall Basic を使ったのですが、うまく動作しなかったため Standard に切り替えました。Azure Firewall Basic では管理トラフィックが AzureFirewallManagmentSubnet 経由となりますが、アドレス空間がオンプレ側と重複しているため、Azure Firewall が正常に動作しなかった可能性が高そうです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2266293/4a38a905-9a04-1e21-faf5-c1219e1da660.png)

### まとめ
以下のような構成にて Inbound/Outbound の通信を特定のサブネットのみのピアリングで実現することができました。

Inbound 用のサブネットは最低限、/27 (27 個の IP アドレス)、Outbound 用のサブネットは /26 (59 個の IP アドレス) くらいの IP アドレスがあれば、オンプレとの接続において様々なケースに対応できるようになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2266293/623a9f21-d25d-514f-9360-c8a3b649f5b5.png)

Private の IPv4 アドレスを節約するためには様々な方法がありますが、代表的なサービスと特徴を以下にまとめてみました。

| サービス | Inbound | Outbound |
|-|-|-|
|Application Gateway v2|Private IP のリスナーを利用することで Inbound のシナリオで IP アドレスを節約できる。ただし、専用サブネットが必要であるため Private Endpoint と組み合わせて利用した方が IP アドレスを節約できる。プロトコルによっては TCP プロキシを利用。|接続先のサーバーが少数かつ固定化されているものであれば Application Gateway の利用はできる可能性あり。ただし、Application Gateway 用のサブネットをオンプレと接続する必要がある。プロトコルによっては TCP プロキシ (プレビュー) を利用。|
|Azure Firewall|Private IP の DNAT (プレビュー) を利用することで IP アドレスを節約できる。ただし、専用サブネット (/26) を オンプレと接続する必要がある。|SNAT を利用することで IP アドレスを節約できる。ただし、専用サブネット (/26 ※ Basic SKU は /26 x 2) をオンプレと接続する必要がある。|
|Private Endpoint|Private Endpoint 用のサブネットのみをオンプレと接続することで IP アドレスを節約できる。|対応シナリオなし|