パブリックプレビューの Azure Private Link NSG を試してみた

## 背景と目的

クローズドな VNET 環境から Azure のサービスへ接続する時は、サービスエンドポイントをサブネットに適用して利用する事が一般的だと思います。サービスエンドポイントや今回試してみるプライベートリンク（ VNET 内にプライベートエンドポイントを作る）を利用しても、今までは NSG で細かいアクセス制御をする事が出来ませんでした。パブリックプレビューですが、プライベートリンクを使用した Azure サービスへのアクセスに対して、NSG で制御出来るかを試してみました。

## 前提条件

コマンドの実施環境は、Mac + Azure CLI です。

```bash:bash
$ sw_vers
ProductName:    macOS
ProductVersion: 11.5.2
BuildVersion:   20G95

$ az version
{
  "azure-cli": "2.27.2",
  "azure-cli-core": "2.27.2",
  "azure-cli-telemetry": "1.0.6",
  "extensions": {}
}
```

## クローズドな VNET 環境を作成してプライベートエンドポイント経由で静的ウェブサイトにアクセスする

まずは、ストレージアカウントの静的ウェブサイトを作成して、プライベートエンドポイント経由で仮想マシンからアクセスしてみます。なお現時点では一部のリージョンしか Private Link NSG に対応していないため、WestUS2 を使用します。

```bash:bash
# 環境変数をセットします
region=westus2
prefix=mnrpeweb

# リソースグループを作成します
az group create \
  --name ${prefix}-rg \
  --location $region

# VNET を作成します
az network vnet create \
  --resource-group ${prefix}-rg \
  --name ${prefix}-vnet \
  --address-prefixes 10.1.0.0/16 \
  --subnet-name default-subnet \
  --subnet-prefix 10.1.0.0/24

# NSG を作成します
az network nsg create \
  --resource-group ${prefix}-rg \
  --name ${prefix}-nsg

# NSG をサブネットに登録します
az network vnet subnet update \
  --resource-group ${prefix}-rg \
  --vnet-name ${prefix}-vnet \
  --name default-subnet \
  --network-security-group ${prefix}-nsg

# プライベートエンドポイントのネットワークポリシーを無効にします
az network vnet subnet update \
  --name default-subnet \
  --resource-group ${prefix}-rg \
  --vnet-name ${prefix}-vnet \
  --disable-private-endpoint-network-policies true

# 仮想マシンを作成します（後述の az vm run-command invoke を使い、SSH は使用しないのでパスワードは何でも良いです）
az vm create \
  --resource-group ${prefix}-rg \
  --name ${prefix}-vm \
  --os-disk-name ${prefix}-vmOSDisk \
  --image CentOS \
  --size Standard_A1_v2 \
  --admin-username azureuser \
  --admin-password $(openssl rand -base64 16) \
  --vnet-name ${prefix}-vnet \
  --subnet default-subnet \
  --nsg "" \
  --public-ip-address "" \
  --storage-sku Standard_LRS

# ストレージアカウントを作成します
az storage account create \
  --name ${prefix}stor \
  --resource-group ${prefix}-rg \
  --https-only true \
  --sku Standard_LRS

# ストレージアカウントのキーを取得します
storagekey=$(az storage account keys list \
  --account-name ${prefix}stor \
  --resource-group ${prefix}-rg \
  --query [0].value \
  --output tsv)

# 静的ウェブサイトを有効にします
az storage blob service-properties update \
  --account-name ${prefix}stor \
  --account-key $storagekey \
  --index-document index.html \
  --404-document error.html \
  --static-website true

# HTML コンテンツを作成します
echo "<h1>${prefix} test</h1>" > index.html

# HTML をストレージアカウントにアップロードします
az storage blob upload \
  --account-name ${prefix}stor \
  --account-key $storagekey \
  --container-name \$web \
  --file index.html \
  --name index.html

# 静的ウェブサイトの URL を取得します
weburl=$(az storage account show \
  --name ${prefix}stor \
  --resource-group ${prefix}-rg \
  --query primaryEndpoints.web \
  --output tsv)

# 静的ウェブサイトにアクセスしてみます（この時点ではグローバル経由でアクセスします）
curl -s $weburl

# グローバル経由でストレージアカウントにアクセス出来ないようにします
az storage account update \
  --resource-group ${prefix}-rg \
  --name ${prefix}stor \
  --default-action Deny

# 静的ウェブサイトにはアクセス出来なくなった事を確認します
curl -s $weburl

# ストレージアカウントのプライベートリンクを作成します
az network private-endpoint create \
  --connection-name ${prefix}-pe-stor \
  --name ${prefix}-pe-stor \
  --resource-group ${prefix}-rg \
  --vnet-name ${prefix}-vnet \
  --subnet default-subnet \
  --group-id web \
  --private-connection-resource-id $(az storage account show \
  --name ${prefix}stor \
  --resource-group ${prefix}-rg \
  --query id \
  --output tsv)

# ストレージアカウントの名前解決のためにプライベート DNS ゾーンを作成します
az network private-dns zone create \
  --resource-group ${prefix}-rg \
  --name "privatelink.web.core.windows.net"

# DNS ゾーンを VNET に登録します
az network private-dns link vnet create \
  --resource-group ${prefix}-rg \
  --zone-name "privatelink.web.core.windows.net" \
  --name ${prefix}-dnslink-stor \
  --virtual-network ${prefix}-vnet \
  --registration-enabled false

# DNS ゾーングループを作成します（ストレージアカウントの DNS レコードが作成されます）
az network private-endpoint dns-zone-group create \
  --resource-group ${prefix}-rg \
  --endpoint-name ${prefix}-pe-stor \
  --name ${prefix}-zone-stor \
  --zone-name ${prefix}-zone-stor \
  --private-dns-zone "privatelink.web.core.windows.net"

# 仮想マシンからプライベートエンドポイント経由で静的ウェブサイトにアクセスしてみます
# HTML コンテンツが返されます
az vm run-command invoke \
  --command-id RunShellScript \
  --resource-group ${prefix}-rg \
  --name ${prefix}-vm \
  --query "value[0].message" \
  --output tsv \
  --scripts "curl -s $weburl"

# 仮想マシンからストレージアカウントのプライベートエンドポイントに対して NSG を設定します
az network nsg rule create \
  --resource-group ${prefix}-rg \
  --nsg-name ${prefix}-nsg \
  --name Deny-VM-Stor \
  --priority 100 \
  --source-address-prefixes 10.1.0.4/32 \
  --destination-address-prefixes 10.1.0.5/32 \
  --destination-port-ranges 443 \
  --direction Inbound \
  --access Deny \
  --protocol Tcp

# もう一度、仮想マシンからプライベートエンドポイント経由で静的ウェブサイトにアクセスしてみます
# NSG で制限しても、HTML コンテンツが返されます
az vm run-command invoke \
  --command-id RunShellScript \
  --resource-group ${prefix}-rg \
  --name ${prefix}-vm \
  --query "value[0].message" \
  --output tsv \
  --scripts "curl -s $weburl"

Enable succeeded: 
[stdout]
<h1>mnrpeweb test</h1>

[stderr]
```

## Private Link NSG を有効にして NSG で制御出来ることを確認する

今回のプレビュー機能を使って、プライベートエンドポイントのネットワークポリシーを有効にします。

```bash:bash
# Private Link NSG を有効にします
az feature register \
  --namespace Microsoft.Network \
  --name AllowPrivateEndpointNSG

# AllowPrivateEndpointNSG を伝達させます
az provider register \
  -n Microsoft.Network

# Private Link NSG が有効になった事を確認します（Registered と表示されます）
az feature register \
  --namespace Microsoft.Network \
  --name AllowPrivateEndpointNSG \
  --query properties.state \
  --output tsv

# プライベートエンドポイントのネットワークポリシーを有効にします
az network vnet subnet update \
  --name default-subnet \
  --resource-group ${prefix}-rg \
  --vnet-name ${prefix}-vnet \
  --disable-private-endpoint-network-policies false

# （参考）プレビュー機能を有効にしていない場合は下記のメッセージが表示されます
(PrivateEndpointNetworkPoliciesCannotBeEnabledOnPrivateEndpointSubnet) Private endpoint network policies cannot be enabled on private endpoint subnet

# 仮想マシンからプライベートエンドポイント経由で静的ウェブサイトにアクセスしてみます
# NSG で制限出来たので、HTML コンテンツが返されなくなりました
az vm run-command invoke \
  --command-id RunShellScript \
  --resource-group ${prefix}-rg \
  --name ${prefix}-vm \
  --query "value[0].message" \
  --output tsv \
  --scripts "curl -v $weburl"

Enable succeeded: 
[stdout]

[stderr]
  0     0    0     0    0     0      0      0 --:--:--  0:02:07 --:--:--     0* Connection timed out
* Failed connect to mnrpewebstor.z5.web.core.windows.net:443; Connection timed out
* Closing connection 0
curl: (7) Failed connect to mnrpewebstor.z5.web.core.windows.net:443; Connection timed out
```

## 参考

作成したリソースを削除します。

```bash:bash
# リソースグループを削除します
az group delete \
  --name ${prefix}-rg
```

下記は、参考サイトです。

[Public preview of Private Link Network Security Group Support](https://azure.microsoft.com/ja-jp/updates/public-preview-of-private-link-network-security-group-support/)

[クイックスタート: Azure CLI を使用してプライベート エンドポイントを作成する](https://docs.microsoft.com/ja-jp/azure/private-link/create-private-endpoint-cli)

[プライベート エンドポイントのネットワーク ポリシーを管理する](https://docs.microsoft.com/ja-jp/azure/private-link/disable-private-endpoint-network-policy)