Azure の Point-to-Site (P2S) VPN を Microsoft Entra ID 認証で構築し、Private Endpoint 経由で Storage Account にアクセスする最小構成を Bicep + azd で組んで学習しました。
「Private Endpoint と Private DNS Zone を作れば VPN クライアントから Storage の FQDN が Private IP に解決されるはず」と思って始めたのですが、想像と違う動きにいくつも詰まりました。本記事は、その「想像と違う」ポイントを整理した記録です。
- 想定読者: Azure の閉域構成や P2S VPN を初めて触る・整理したい Azure / MS エンジニア
-
得られる知見:
- P2S VPN を Bicep で IaC 化する最小構成
- DNS と TCP は別レイヤー であることを実感する設計
- P2S クライアントは Private DNS Zone を自動解決できない という公式仕様
- Private Endpoint は ICMP に応答しない 仕様
- WSL2 + Azure VPN Client の組み合わせで意外と動く件
結論
- ✅ P2S VPN + AAD 認証 + Private Endpoint + Private DNS Zone を Bicep でフルコード化できた
- ✅ デプロイ時間は VPN Gateway がほぼ全てで 約 35 分
- ⚠️ Private DNS Zone を作っても P2S クライアントからは自動解決されない(hosts / DNS Resolver / NRPT が必要)
- ⚠️
publicNetworkAccess: Disabledでもパブリック DNS には常に Public IP が公開される(DNS と TCP は完全に別レイヤー) - ⚠️ Private Endpoint は ICMP 非対応(
pingのタイムアウトは正常) - ⚠️ VPN Gateway は停止不可 で、
VpnGw1AZで月額 $150 以上の時間課金が継続発生。学習用途ではazd down必須
アーキテクチャ
環境
| 項目 | バージョン / 値 |
|---|---|
| Azure CLI | 2.x |
| azd | 1.24.x |
| Bicep | 0.41.x |
| VPN Gateway SKU |
VpnGw1AZ (Generation2) |
| クライアント Pool | 172.16.0.0/24 |
| リージョン | japaneast |
| Audience ID (Microsoft 登録 Azure VPN Client) | c632b3df-fb67-4d84-bdcf-b95ad541b5c8 |
構築手順
Step 1: VNet と GatewaySubnet
GatewaySubnet は 予約名(変更不可)。サイズは /27 以上推奨。
// IMPORTANT: GatewaySubnet is a RESERVED name. Do not change it.
var gatewaySubnetName = 'GatewaySubnet'
var gatewaySubnetPrefix = '10.10.255.0/27'
resource vnet 'Microsoft.Network/virtualNetworks@2023-11-01' = {
name: 'vnet-vpn-learn'
location: location
tags: tags
properties: {
addressSpace: { addressPrefixes: ['10.10.0.0/16'] }
subnets: [
{ name: gatewaySubnetName, properties: { addressPrefix: gatewaySubnetPrefix } }
{
name: 'snet-private-endpoint'
properties: {
addressPrefix: '10.10.1.0/24'
privateEndpointNetworkPolicies: 'Disabled'
}
}
]
}
}
Step 2: VPN Gateway (Entra ID 認証)
ポイントは 3 つ:
-
SKU は
VpnGw1AZ必須(2025-11-01 以降、非 AZ SKU は新規作成不可) - Generation2 必須(AZ SKU は Gen1 非対応)
- OpenVPN + AAD の組み合わせ(IKEv2/SSTP では AAD 認証不可)
// Microsoft が登録済みの Azure VPN Client App。管理者同意は不要。
var azureVpnClientAppId = 'c632b3df-fb67-4d84-bdcf-b95ad541b5c8'
var aadTenantUrl = '${environment().authentication.loginEndpoint}${tenantId}/'
#disable-next-line no-hardcoded-env-urls
var aadIssuerUrl = 'https://sts.windows.net/${tenantId}/'
resource vpnGateway 'Microsoft.Network/virtualNetworkGateways@2023-11-01' = {
name: 'vpngw-vpn-learn'
location: location
properties: {
gatewayType: 'Vpn'
vpnType: 'RouteBased'
vpnGatewayGeneration: 'Generation2'
sku: { name: 'VpnGw1AZ', tier: 'VpnGw1AZ' }
ipConfigurations: [/* 省略: Public IP + GatewaySubnet */]
vpnClientConfiguration: {
vpnClientAddressPool: { addressPrefixes: ['172.16.0.0/24'] }
vpnClientProtocols: ['OpenVPN']
vpnAuthenticationTypes: ['AAD']
aadTenant: aadTenantUrl
aadAudience: azureVpnClientAppId
aadIssuer: aadIssuerUrl
}
}
}
Step 3: Storage Account を最初から閉域に
「後から publicNetworkAccess を Disabled に切り替える」のではなく、作成時から閉じるのが安全。
resource storageAccount 'Microsoft.Storage/storageAccounts@2023-05-01' = {
name: storageAccountName
sku: { name: 'Standard_LRS' }
kind: 'StorageV2'
properties: {
minimumTlsVersion: 'TLS1_2'
allowBlobPublicAccess: false
publicNetworkAccess: 'Disabled'
networkAcls: { defaultAction: 'Deny', bypass: 'AzureServices' }
}
}
Step 4: Private Endpoint + Private DNS Zone
Private DNS Zone Group を関連付けると、Azure が A レコードを 自動登録 してくれる(PE の IP が変わっても追従)。
resource privateDnsZone 'Microsoft.Network/privateDnsZones@2024-06-01' = {
name: 'privatelink.blob.${environment().suffixes.storage}'
location: 'global'
}
resource vnetLink 'Microsoft.Network/privateDnsZones/virtualNetworkLinks@2024-06-01' = {
parent: privateDnsZone
name: 'vnet-link-vpn-learn'
location: 'global'
properties: {
virtualNetwork: { id: vnetId }
registrationEnabled: false
}
}
resource privateEndpoint 'Microsoft.Network/privateEndpoints@2023-11-01' = {
name: 'pep-<storage-name>-blob'
location: location
properties: {
subnet: { id: privateEndpointSubnetId }
privateLinkServiceConnections: [
{
name: 'plsc-blob'
properties: {
privateLinkServiceId: storageAccountId
groupIds: ['blob']
}
}
]
}
}
resource privateDnsZoneGroup 'Microsoft.Network/privateEndpoints/privateDnsZoneGroups@2023-11-01' = {
parent: privateEndpoint
name: 'default'
properties: {
privateDnsZoneConfigs: [
{
name: 'blob'
properties: { privateDnsZoneId: privateDnsZone.id }
}
]
}
dependsOn: [vnetLink]
}
Step 5: デプロイ
cd vpn-gateway
azd auth login
azd env new vpn-learn-001
azd env set AZURE_TENANT_ID <your-tenant-id>
azd up
⏰ VPN Gateway の作成で約 35 分かかります(Storage / PE / VNet は数十秒)。
リソース確認
VNet
Subnet snet-private-endpoint のNSG
接続デバイス
動かして確認
ステップ 1: VPN クライアント設定パッケージのダウンロード
- Azure Portal で 仮想ネットワークゲートウェイ を開く
- リソースグループ:
azd env get-value AZURE_RESOURCE_GROUP_NAMEで取得した名前 - 名前:
vpngw-vpn-learn
- リソースグループ:
- 左側メニュー → 「ポイント対サイトの構成」 をクリック
- 上部の 「VPN クライアントのダウンロード」 をクリック
- zip ファイルがダウンロードされる
- zip を解凍し、
AzureVPN/azurevpnconfig.xmlを確認
ステップ 2: Windows 10 / 11 へのインストール
Azure VPN Client のインストール
- Microsoft Store を開く
- 「Azure VPN Client」を検索してインストール
設定ファイルのインポート
- Azure VPN Client を起動
- 左下の 「+」 → 「インポート」 をクリック
- ステップ 1 で解凍した
AzureVPN/azurevpnconfig.xmlを選択 - 接続情報が表示されるので 「保存」 をクリック
接続
- Azure VPN Client で接続プロファイルを選択
- 「接続」 をクリック
- ブラウザに Entra ID のサインイン画面が表示される
- サインインすると VPN Client に戻り、「接続済み」になる
ステップ 3: 接続確認
接続後、以下のいずれかで VPN クライアント IP がアサインされていることを確認します。
PowerShellで出力中に以下のような PPP/Tunnel アダプター で 172.16.0.x の IP が出ていれば成功:
ipconfig
<中略>
PPP アダプター vnet-vpn-learn:
接続固有の DNS サフィックス . . . . .:
IPv4 アドレス . . . . . . . . . . . .: 172.16.0.2
サブネット マスク . . . . . . . . . .: 255.255.255.255
デフォルト ゲートウェイ . . . . . . .:
Storage Account接続
以下のPowerShell実行でStorage AccountのIPアドレスを取得。
$NIC_ID = az network private-endpoint show -g rg-vpn-learn-001 -n pep-stvpngwuqem7g53eqrtw-blob --query "networkInterfaces[0].id" -o tsv
az network nic show --ids $NIC_ID --query "ipConfigurations[0].privateIPAddress" -o tsv
Hosts に追記。
10.10.1.4 stvpngwuqem7g53eqrtw.blob.core.windows.net
VPN接続状態で、以下のコマンドでPublic IPが返る。DNSは登録されているので問題なし。
nslookup stvpngwuqem7g53eqrtw.blob.core.windows.net
pingは失敗するがIPアドレスはhostsを見れている。pingが届かないのは想定通り。
ping stvpngwuqem7g53eqrtw.blob.core.windows.net
stvpngwuqem7g53eqrtw.blob.core.windows.net [10.10.1.4]に ping を送信しています 32 バイトのデータ:
要求がタイムアウトしました。
要求がタイムアウトしました。
要求がタイムアウトしました。
要求がタイムアウトしました。
10.10.1.4 の ping 統計:
パケット数: 送信 = 4、受信 = 0、損失 = 4 (100% の損失)、
VPN接続時で、azコマンドなら大丈夫。
> az storage container list `
>> --account-name stvpngwuqem7g53eqrtw `
>> --auth-mode login `
>> --output table
Name Lease Status Last Modified
------ -------------- -------------------------
test 2026-05-16T06:34:19+00:00
VPN切断時だと以下のようなエラー。
az storage container list `
>> --account-name stvpngwuqem7g53eqrtw `
>> --auth-mode login `
>> --output table
(<HTTPSConnection(host='stvpngwuqem7g53eqrtw.blob.core.windows.net', port=443) at 0x21175477390>, 'Connection to stvpngwuqem7g53eqrtw.blob.core.windows.net timed out. (connect timeout=None)')
VPN接続時だとデータプレーンをPortalから参照可能。
VPN切断時だとデータプレーンをPortalから参照不可。
ハマりポイント
① publicNetworkAccess: Disabled でも DNS には Public IP が公開される
最初の混乱はこれでした。「Storage を閉域にしたなら DNS も引けなくなるのでは?」と思いきや、nslookup すると堂々と Public IP が返ってきます。
Storage Account の publicNetworkAccess: Disabled は TCP/HTTP レイヤーのファイアウォール設定であり、DNS レコードを消すものではない。
CNAME チェーンを dig +noshort で見るとよく分かります:
stvpngw<suffix>.blob.core.windows.net
→ (CNAME) stvpngw<suffix>.privatelink.blob.core.windows.net
→ (CNAME) blob.tyo22prdstr09a.store.core.windows.net
→ (A) 20.60.xxx.xxx ← Public IP
つまり Azure は どんな Storage Account にも常にパブリック DNS レコードを公開 していて、publicNetworkAccess の設定はあくまで「その IP への TCP 接続を Storage 側で受け付けない」という設定。
| クライアント | DNS 解決結果 | TCP アクセス |
|---|---|---|
| インターネット | Public IP | Storage が拒否 (Disabled) |
| VNet 内 + Private DNS Zone | Private IP | PE 経由で成功 |
| P2S クライアント (デフォルト) | Public IP | Public IP に向かい Storage 拒否 |
| P2S クライアント + hosts | Private IP (強制) | VPN 経由で PE に成功 |
privatelink.* CNAME が CNAME チェーンに必ず挟まっているのは、Private DNS Zone を作ったときに同名ゾーンを上書きできるようにするための仕掛け です。これが Private Link の本質。
② P2S クライアントは Private DNS Zone を自動解決できない
ここが最大の罠。「VNet に Private DNS Zone をリンクしたから、VPN 経由でも引けるはず」と思っていたら、引けません。
Azure VPN P2S クライアントは、VNet にリンクされた Private DNS Zone を自動では解決できない。
公式根拠:
- Azure VPN Client - configure optional DNS and routing settings — カスタム DNS は XML 手動編集が必要
-
Azure Files networking considerations — Azure DNS (
168.63.129.16) は VNet 内からのみ到達可能 な特殊 IP
本番での解決策は通常 Azure DNS Private Resolver の inbound endpoint を VNet に立てて、P2S クライアントの DNS をそこに向ける構成(または DNS フォワーダー VM)。
学習用途では公式ドキュメントにも紹介されている hosts ファイル手動追記 で回避できます。
# PowerShell 管理者として実行
$PE_IP = az network private-dns record-set a list `
-g rg-vpn-learn-001 `
-z privatelink.blob.core.windows.net `
--query "[0].aRecords[0].ipv4Address" -o tsv
$FQDN = "<storage-name>.blob.core.windows.net"
Add-Content -Path C:\Windows\System32\drivers\etc\hosts -Value "$PE_IP`t$FQDN"
🎓 「Private DNS Zone は作るのに無駄では?」 → そんなことはなく、VNet 内クライアント(VM / Function / Container Apps)からはちゃんと引けます。「誰から引けて誰から引けないか」を理解するのが Private Link 学習の核心。
③ Private Endpoint は ICMP に応答しない
hosts を書いて意気揚々と ping したら全部タイムアウト。慌てます。
Private Endpoint は ICMP 非対応(公式仕様)。ping のタイムアウトは正常動作。
公式根拠: Azure Private Link FAQ - Do private endpoints support ICMP traffic?
"TCP and UDP traffic are only supported for a private endpoint."
疎通確認は TCP 443 で行います:
Test-NetConnection -ComputerName <storage-name>.blob.core.windows.net -Port 443
# TcpTestSucceeded : True なら OK
Linux / Mac は Bash の /dev/tcp 疑似デバイスが楽:
timeout 5 bash -c "echo > /dev/tcp/<storage-name>.blob.core.windows.net/443 && echo OK || echo NG"
④ Private Endpoint の IP の取り方(customDnsConfigs の罠)
PE の IP を取ろうとして、こう書きたくなります:
# ❌ これでは IP が取れない(customDnsConfigs が空のことが多い)
az network private-endpoint show -g $RG -n pep-... \
--query "customDnsConfigs[0].ipAddresses[0]" -o tsv
customDnsConfigs は subResource ごとの追加 FQDN 情報用フィールドで、Storage の blob のような基本 subResource では 空でした。正しい取り方は 2 通り:
# 方法 A: Private DNS Zone の A レコードから(最も楽)
az network private-dns record-set a list \
-g $RG -z privatelink.blob.core.windows.net \
--query "[0].aRecords[0].ipv4Address" -o tsv
# 方法 B: PE 経由で NIC リソースから
NIC_ID=$(az network private-endpoint show -g $RG -n pep-... \
--query "networkInterfaces[0].id" -o tsv)
az network nic show --ids "$NIC_ID" \
--query "ipConfigurations[0].privateIPAddress" -o tsv
🎓 Private Endpoint は実は「PE リソース + NIC リソース + Private DNS A レコード」の 3 つの組 で構成されている。メインの IP は NIC リソースに格納される。
⑤ WSL2 + Azure VPN Client で意外と動いた話
Azure VPN Client は Linux 非対応なので、WSL2 ユーザーは Windows ホスト側で接続するしかありません。一般的に WSL2 (NAT モード) は VPN ルーティングを引き継がない、と覚えていました。
ところが Windows 11 + WSL2 (Hyper-V firewall + DNS Tunneling) では、WSL からも普通に Private Endpoint に TCP 443 で到達できました。
# WSL 内から実行
$ powershell.exe -Command "Get-NetIPAddress -InterfaceAlias 'vnet-vpn-learn'"
IPAddress : 172.16.0.2 # ← Windows 側で VPN 接続済み
$ timeout 5 bash -c "echo > /dev/tcp/10.10.1.4/443 && echo OK"
OK # ← WSL から PE に到達できている
しかも dig で Private DNS Zone まで引けてしまうケースがあり、これは Azure VPN Client が NRPT (Name Resolution Policy Table) を使う ためと推測されます。
Microsoft 公式 Azure VPN Client docs より:
"When using Microsoft Entra ID authentication, the Azure VPN Client utilizes DNS Name Resolution Policy Table (NRPT) entries"
NRPT は 特定ドメインだけ別 DNS に転送する Windows 機能。Win11 22H2 以降の WSL2 DNS Tunneling と組み合わさると、結果として WSL からも Private DNS Zone が引けてしまうことがある。
つまり「P2S では Private DNS Zone を引けない」は デフォルト構成限定 の話で、Azure VPN Client + NRPT + 適切な DNS サーバー指定があれば引けます。
検証結果
| シナリオ | 結果 | 備考 |
|---|---|---|
Bicep az bicep build
|
✅ | warnings 0 |
azd up 全体 |
✅ | 約 35 分 (VPN GW がほぼ全て) |
| Azure VPN Client サインイン | ✅ | Entra ID + OpenVPN |
接続後 nslookup (デフォルト構成) |
⚠️ Public IP が返る | 想定通り(DNS 制限) |
hosts 追加後 nslookup
|
✅ Private IP 10.10.1.4
|
回避策成功 |
ping (ICMP) |
❌ タイムアウト | 仕様通り(ICMP 非対応) |
Test-NetConnection -Port 443 |
✅ True | TCP 疎通 OK |
az storage container list (接続中) |
✅ コンテナ表示 | PE 経由で成功 |
az storage container list (切断後) |
❌ タイムアウト | publicNetworkAccess Disabled の効果 |
azd down --purge --force |
✅ 全削除 | 約 15 分 |
まとめ
学び
- Private Link は DNS と TCP を別々に設計 する必要がある
- Private DNS Zone は VNet 内クライアント用 であり、P2S クライアントは別途仕組みが必要
- Private Endpoint は L4 (TCP/UDP) のみ、ICMP は仕様外
- VPN Gateway は 時間課金が止められないので、学習後は必ず削除(
azd down)
限界・残課題
- 本記事では DNS Private Resolver を使わず hosts で回避した。次は Resolver を Bicep で組んで「本番相当」を試したい
- VPN Gateway デプロイの 35 分は短縮不可。学習用としては大きな心理的コスト
- Azure VPN Client の Linux サポートが無いのは継続的な不便。Microsoft の Public Roadmap で進展を期待
次にやりたいこと
- Azure DNS Private Resolver を追加して「P2S → Private DNS Zone」を正規ルートで通す
- VNet Peering を組んで複数 VNet 間の名前解決と P2S の関係を確認
- Always-On VPN プロファイル設定の自動化
参考リンク
- Azure VPN Client - configure optional DNS and routing settings
- Azure Private Link FAQ — Do private endpoints support ICMP?
- Azure Private Endpoint DNS integration Scenarios
- Azure Files networking considerations - Private endpoints
- P2S VPN with Microsoft Entra authentication
- What is Azure DNS Private Resolver?
- Troubleshooting WSL - VPN connectivity





