Windows Server 2025でSMB over QUICを実装してみた(ドメイン環境)02 ～クライアントPC準備編～

はい。
図らずもシリーズものとなってしまいました。
[以前の記事](https://qiita.com/hidekko/items/385301500337dbbca498)のドメイン環境編です。
やっぱりエンタープライズ環境を準備するのは大変ですね。
**めんどいわー**
<font color="Red">でもまぁこの面倒さになんやかんや言いながら楽しみを見出すあたりがインフラエンジニアかなと思います。</font>

# なんでクライアントPCをわざわざ準備する必要があんの？手元のWindows PCでやったらええやん
そうなんですよ。
その通りですよね。
いや、ホンマ、ごもっとも。
ちなみにWindows 11 Proが実機で2台と、Windows 10 Proが実機で1台、仮想環境上にWindows 10 Proが1台あります。
全部ちゃんとライセンス認証しています。
全部Proなんで、ドメイン参加できます。
もちろんドメインコントローラーも仮想環境上にあり、仮想環境上と実機のあるセグメントは分かれていますがAny Any Permitで接続可能なので、実機はドメイン参加できる環境です。

# ほんならなんでわざわざクライアントPC準備するん？
いや、ホンマそうなんですよ。
ただ、この実機のWindows 11/10 Pro 3台、<font color="Red">**全部Microsoft Entra ID Joinしてるんですよ。**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/643392/7ce1de9e-c4ec-6b66-dbd2-0a591dd5c679.png)

<font color="Red">**Microsoft Entra ID Joinを先に行うと、ドメイン参加できないんですよね。**
**これ、Windowsの仕様です。**</font>
もう赤字ばっかりでなんのこっちゃ分かんないですね。

# いや、ほんならMicrosoft Entra ID Join外したらええやん
いや、ホンマそうなんですよ。
マジで。
ホンマその通り。
おっしゃる通りですわ。
**でもね、我が家の環境、<font color="Red">条件付きアクセスやってるんですが、その条件にBit Locker入れてるんですよね。</font>**
**<font color="Red">Bit Locker解除する(複合する)の、めっちゃ時間かかりますやん？</font>**
**<font color="Red">もうこのBit Locker解除が面倒すぎるんで諦めたんですよ。</font>**
**<font color="Red">もうね、Bit Locker解除とか条件付きアクセスの変更とか、Intuneの解除とか、もろもろ面倒すぎるんですよ・・・</font>**

# 1台Microsoft Entra ID Registeredあるやん
あ、気付きました？
そうなんですよ。
Win10Pro-KVMね。
もう1台Microsoft Entra ID Registeredの端末ありますけど、iPhoneなんで今回対象外ですよね。
**このWin10Pro-KVMね、その名の通り仮想環境上のWin10 Proなんですよ。
なので今回の検証に使えないんですよ。
今回SMB over QUICなんで、ドメインに参加しつつ、<font color="Red">ドメイン配下のファイルサーバーに社外からアクセスするクライアントPCを用意しなきゃいけないんで、これがドメコンと一緒に載ってる仮想環境上だと無理なんですよね。</font>**
仮想スイッチでセグメント変えることも考えましたけど、仮想サーバの物理NIC1枚で、しかもその1枚をBridgeでやってるんで無理っぽいんですよね。
Windows Firewallでファイルサーバと直接アクセス不可にするとかも考えたんですが、これだとちょっと不自然ですよね。
<font color="Red">検証の客観性に欠けるかなって。</font>(←めんどくさい以外で唯一まともな理由)

いや、一応一通り試したんですよ。
手元の実機使ってできへんかなって。

<font color="Red">上述の通り全部無理でした。</font>
もうね、心折れそうですよ。
しかも全部自分のせいですからね。

# アホなん？個人の環境にこだわりすぎなんちゃうん？作り込みすぎなんちゃうん？
いや、ホンマ、そうなんですよ。
ぐうの音も出ませんわ。
マジで。
**<font color="Red">でも面倒なのは面倒なので実施したくないですし、検証終わってから切り戻すこと考えたらもうマジでこの検証やんのイヤになるんで、やりたくないです。</font>**

# で、結局どうするん？
**<font color="Red">考え方を変えてAzure上にWindows 11 Pro構築して、そっちをオンプレミスのドメイン環境に参加させます。</font>**

はい。
今回もめっちゃ前提長かったですね。
ここまで来るのにいろいろトライ＆エラー繰り返したのはなんとなくお分かりいただけたと思います。(伝わったかどうかは別として)

# Windows 11 Proとドメイン参加のために仮想ネットワークゲートウェイ、ローカルネットワークゲートウェイ、VPN接続のそれぞれを全部Terraformでコード化する
はい。
やっと本記事の本編です。
自分で言うのもなんなんですが、私はQiita上でもよう喋りますね。
ではコードいってみましょう。

# Terraform

## 変数設定
```varriables.tf
# Import Subscription ID variable from OS's Enviroment variable
variable "ARM_SUBSCRIPTION_ID" {
  type        = string
}

# Import Tenant ID variable from OS's Enviroment variable
variable "ARM_TENANT_ID" {
  type        = string
}

# Import Client ID variable from OS's Enviroment variable
variable "ARM_CLIENT_ID" {
  type        = string
}

# Import Client Secret variable from OS's Enviroment variable
variable "ARM_CLIENT_SECRET" {
  type        = string
}

# Generate random text for a unique storage account name
resource "random_id" "random_id" {
  keepers = {
    # Generate a new ID only when a new resource group is defined
    resource_group = azurerm_resource_group.rg.name
  }
  byte_length = 8
}

# system name
variable "system_name" {
  type        = string
  default     = "win11-01"
}

# region
variable "region" {
  type        = string
  default     = "southeastasia"
}

# resource group (rg) name prefix
variable "rg_name_pre" {
  type        = string
  default     = "rg-"
}

# virtual network (vnet) name prefix
variable "vnet_name_pre" {
  type        = string
  default     = "vnet-"
}

# virtual network (vnet) name suffix for client
variable "vnet_name_suf01" {
  type        = string
  default     = "client01"
}

# vnet ip address prefix01 for client
variable "vnet_addr_pre01" {
  type        = string
  default     = "192.168."
}

# vnet ip address suffix for client
variable "vnet_addr_suf01" {
  type        = string
  default     = "200.0/23"
}

# subnet (sn) name prefix
variable "sn_name_pre" {
  type        = string
  default     = "sn-"
}

# sn for client name suffix for client
variable "sn_name_suf01" {
  type        = string
  default     = "client01"
}

# sn for clinet ip address suffix for client
variable "sn_addr_suf01" {
  type        = string
  default     = "201.0/24"
}

# sn for clinet ip address suffix for Virtual Network Gateway
variable "sn_addr_suf02" {
  type        = string
  default     = "200.0/24"
}

# Network Interface Card (nic) name prefix
variable "nic_name_pre" {
  type        = string
  default     = "nic-"  
}

# Network Security Group (nsg) name prefix
variable "nsg_name_pre" {
  type        = string
  default     = "nsg-"  
}

# Public IP Address (pip) prefix
variable "pip_name_pre" {
  type        = string
  default     = "pip-"
}

# IP Configuration (ipconf) suffix
variable "ipconf_suf" {
  type        = string
  default     = "-configuration"
}

# Private IP Address Allocation (priv_ip_alloc)
variable "priv_ip_alloc" {
  type        = string
  default     = "Static" # or Dynamic
}

# admin user (adminuser) name
variable "adminuser" {
  type        = string
  default     = "init-Admin001"
}

# Virtual Machine OS Disk (osdisk) name prefix
variable "vm_osdisk_pre" {
  type        = string
  default     = "osdisk-"
}

# Virtual Machine OS Disk SKU (osdisk_sku)
variable "vm_osdisk_sku" {
  type        = string
  default     = "StandardSSD_LRS"
}

# Virtual Machine SKU (vmsku) name
variable "vm_sku" {
  type        = string
  default     = "Standard_D4_v5"
}

# Virtual Machine (vm) name prefix
variable "vm_name_pre" {
  type        = string
  default     = "vm-"
}

# vm name suffix01
variable "vm_name_suf01" {
  type        = string
  default     = "client01"
}

# Virtual Machine OS Publisher (os_pub) name
# "MicrosoftWindowsDesktop" is Client OS 
# "MicrosoftWindowsServer" is Server OS
# "Canonical" is Ubuntu
# "almalinux" is Almalinux
variable "os_pub01" {
  type        = string
  default     = "MicrosoftWindowsDesktop"
}

# Virtual Machine OS Offer (os_offer) name
# "Windows-10" is Windows 10 
# "Windows-11" is Windows 11
# "WindowsServer" os Windows Server OS
# "0001-com-ubuntu-server-jammy" is Ubuntu
# "almalinux-x86_64" is Almalinux
variable "os_offer01" {
  type        = string
  default     = "Windows-11"
}

# Virtual Machine OS (os_sku) name
# 2016-Datacenter
# 2019-Datacenter
# 2022-datacenter-azure-edition
# 22_04-lts-gen2 (Ubuntu 22.04 LTS)
# 9-gen2 (Almalinux 9)
variable "os_sku01" {
  type        = string
  default     = "win11-22h2-pro"
}

# Virtual Machine OS Version (os_ver) name
variable "os_ver" {
  type        = string
  default     = "latest"
}

# Local Gateway name prefix
variable "LG_name_pre" {
  type        = string
  default     = "lg-"
}

# Local Gateway name suffix
variable "LG_name_suf" {
  type        = string
  default     = "RTX1300"
}

# Local Gateway DDNS FQDN
variable "LG_DDNS" {
  type        = string
  default     = "[ご自身の環境のDDNS]"
}

# VPN connection name prefix
variable "connection_name_pre" {
  type        = string
  default     = "con-"
}
```
もうね、あんまり言うことないですわ。
最後から8行目の[ご自身の環境のDDNS]と書いてある部分くらいです。
[いつぞやの記事](https://qiita.com/hidekko/items/6ea27e158b531fad3418)で書いている通りDDNSでAzureはVPN張れるので、今回もこれ使ってます。

## プロバイダー
```providers.tf
terraform {
  required_providers {
        azapi = {
      source  = "azure/azapi"
      version = "~>1.5"
    }
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "~>3.0"
    }
    random = {
      source  = "hashicorp/random"
      version = "~>3.0"
    }
  }
}

provider "azapi" {
}

provider "azurerm" {
  features {}
  subscription_id   = "${var.ARM_SUBSCRIPTION_ID}"
  tenant_id         = "${var.ARM_TENANT_ID}"
  client_id         = "${var.ARM_CLIENT_ID}"
  client_secret     = "${var.ARM_CLIENT_SECRET}"
}
```
Terraform実行時のTerraform自身のパラメータですね。

## リソースグループ作成
```resource_group.tf
# rg Create
resource "azurerm_resource_group" "rg" {
  location = var.region
  name     = "${var.rg_name_pre}${var.system_name}"
}
```
リソースグループ作成ですね。
大した事してません。
変数から全部呼んでるんで、ここだけだと何やってるかわかんないですね。

## 仮想ネットワーク作成
```vnet_subnet.tf
#----------------------------------------
# client vnet Create
#----------------------------------------
resource "azurerm_virtual_network" "vnet-client" {
  resource_group_name = azurerm_resource_group.rg.name
  location = azurerm_resource_group.rg.location
  name     = "${var.vnet_name_pre}${var.vnet_name_suf01}"
  address_space = [ "${var.vnet_addr_pre01}${var.vnet_addr_suf01}" ]
}

# sn-client01 Create
resource "azurerm_subnet" "sn-client01" {
  resource_group_name = azurerm_resource_group.rg.name
  name = "${var.sn_name_pre}${var.sn_name_suf01}"
  virtual_network_name = azurerm_virtual_network.vnet-client.name
  address_prefixes = [ "${var.vnet_addr_pre01}${var.sn_addr_suf01}" ]
  service_endpoints = [ "Microsoft.Storage" ]
}
```
ここも特筆すべき点は無いですね。

## NSG作成
```nsg.tf
#------------------------------------
# nsg-sn-client01 Create
#------------------------------------

resource "azurerm_network_security_group" "nsg-sn-client01" {
  resource_group_name = azurerm_resource_group.rg.name
  location = azurerm_resource_group.rg.location
  name = "${var.nsg_name_pre}${azurerm_subnet.sn-client01.name}"

## InBound Rule
  security_rule  {
    name = "AllowVnetInBound"
    priority = 100
    direction = "Inbound"
    access = "Allow"
    protocol = "*"
    source_port_range = "*"
    destination_port_range = "*"
    source_address_prefix = "VirtualNetwork"
    destination_address_prefix = "VirtualNetwork"
  }

 security_rule  {
    name = "AllowAzureLoadBalancerInBound"
    priority = 101
    direction = "Inbound"
    access = "Allow"
    protocol = "*"
    source_port_range = "*"
    destination_port_range = "*"
    source_address_prefix = "AzureLoadBalancer"
    destination_address_prefix = "*"
  }

  security_rule {
    name = "AllowInBoundSSHOnPremises"
    priority = 1001
    direction = "Inbound"
    access = "Allow"
    protocol = "Tcp"
    source_port_range = "*"
    destination_port_range = "3389"
    source_address_prefix = "[ご自身のグローバルIPアドレス]"
    destination_address_prefix = "*"
  }

  security_rule {
    name = "DenyAllInBound"
    priority = 4096
    direction = "Inbound"
    access = "Deny"
    protocol = "*"
    source_port_range = "*"
    destination_port_range = "*"
    source_address_prefix = "*"
    destination_address_prefix = "*"
  }

## OutBound Rule
  security_rule  {
    name = "AllowVnetOutBound"
    priority = 100
    direction = "Outbound"
    access = "Allow"
    protocol = "*"
    source_port_range = "*"
    destination_port_range = "*"
    source_address_prefix = "VirtualNetwork"
    destination_address_prefix = "VirtualNetwork"
  }

 security_rule  {
    name = "AllowAzureFrontDoor.FirstPartyHttpOutBound"
    priority = 101
    direction = "Outbound"
    access = "Allow"
    protocol = "Tcp"
    source_port_range = "*"
    destination_port_range = "80"
    source_address_prefix = "*"
    destination_address_prefix = "AzureFrontDoor.FirstParty"
    description = "Allow Windows Update Rule01"
  }

 security_rule  {
    name = "AllowAzureUpdateDeliveryHttpsOutBound"
    priority = 102
    direction = "Outbound"
    access = "Allow"
    protocol = "Tcp"
    source_port_range = "*"
    destination_port_range = "443"
    source_address_prefix = "*"
    destination_address_prefix = "AzureUpdateDelivery"
    description = "Allow Windows Update Rule02"
  }

  security_rule {
    name = "AllowAllOutBound"
    priority = 4095
    direction = "Outbound"
    access = "Allow"
    protocol = "*"
    source_port_range = "*"
    destination_port_range = "*"
    source_address_prefix = "*"
    destination_address_prefix = "*"
  }

  security_rule {
    name = "DenyAllOutBound"
    priority = 4096
    direction = "Outbound"
    access = "Deny"
    protocol = "*"
    source_port_range = "*"
    destination_port_range = "*"
    source_address_prefix = "*"
    destination_address_prefix = "*"
  }

}

# Connect nsg-sn-client01 to sn-client01
resource "azurerm_subnet_network_security_group_association" "nsg-sn-client01-to-sn-client01" {
  subnet_id =  azurerm_subnet.sn-client01.id
  network_security_group_id = azurerm_network_security_group.nsg-sn-client01.id
}
```
[ご自身のグローバルIPアドレス]
くらいですかね。

## ストレージカウント作成
```storage.tf
# Create storage account for boot diagnostics
resource "azurerm_storage_account" "storage_account" {
  resource_group_name      = azurerm_resource_group.rg.name
  location                 = azurerm_resource_group.rg.location
  name                     = "stdiag${random_id.random_id.hex}"
  account_tier             = "Standard"
  account_replication_type = "LRS"
}

resource "azurerm_storage_account_network_rules" "for_multiple_subnet_id" {
  storage_account_id = azurerm_storage_account.storage_account.id
  default_action = "Deny"
  virtual_network_subnet_ids = [azurerm_subnet.sn-client01.id]
  ip_rules = ["[ご自身のグローバルIPアドレス]"]
}
```
ここも[ご自身のグローバルIPアドレス]くらいですかね。

## Windows 11 Pro作成
```windows_vm.tf
# --------------------------------------
# Create Windows 11
# --------------------------------------

# pip01 Create
resource "azurerm_public_ip" "pip-client01" {
  name                = "${var.pip_name_pre}${var.vm_name_pre}${var.vm_name_suf01}"
  location            = azurerm_resource_group.rg.location
  resource_group_name = azurerm_resource_group.rg.name
  sku                 = "Standard"
  allocation_method   = "Static"
}

# nic01 (attached for vm01) Create
resource "azurerm_network_interface" "nic-client01" {
  resource_group_name = azurerm_resource_group.rg.name
  location = azurerm_resource_group.rg.location
  name = "${var.nic_name_pre}${var.vm_name_pre}${var.vm_name_suf01}"

  ip_configuration {
    name = "${var.nic_name_pre}${var.vm_name_pre}${var.vm_name_suf01}${var.ipconf_suf}"
    subnet_id = azurerm_subnet.sn-client01.id
    private_ip_address_allocation = "${var.priv_ip_alloc}"
    private_ip_address = "${var.vnet_addr_pre01}201.11"
    public_ip_address_id = azurerm_public_ip.pip-client01.id
  }
}

# windows vm01 Create
resource "azurerm_windows_virtual_machine" "vm01" {
  name                  = "${var.vm_name_pre}${var.vm_name_suf01}"
  admin_username        = "${var.adminuser}"
#  admin_password        = random_password.password.result
  admin_password        = "P@ssw0rd0123"
  location              = azurerm_resource_group.rg.location
  resource_group_name   = azurerm_resource_group.rg.name
  network_interface_ids = [azurerm_network_interface.nic-client01.id]
  size                  = "${var.vm_sku}"
  priority              = "Spot" # or Regular
  eviction_policy       = "Deallocate"
  
  os_disk {
    name                 = "${var.vm_osdisk_pre}${var.vm_name_pre}${var.vm_name_suf01}"
    caching              = "ReadWrite"
    storage_account_type = "${var.vm_osdisk_sku}"
  }

  source_image_reference {
    publisher = "${var.os_pub01}"
    offer     = "${var.os_offer01}"
    sku       = "${var.os_sku01}"
    version   = "${var.os_ver}"
  }

  boot_diagnostics {
    storage_account_uri = azurerm_storage_account.storage_account.primary_blob_endpoint
  }
}
```
全然なんて言うことは無いですね。

## 仮想ネットワークゲートウェイ作成
```vnetgateway.tf
# ------------------------------------------
# Create Virtual Network Gateway Subnet
# ------------------------------------------

resource "azurerm_subnet" "GatewaySubnet" {
  resource_group_name = azurerm_resource_group.rg.name
  name = "GatewaySubnet"
  virtual_network_name = azurerm_virtual_network.vnet-client.name
  address_prefixes = [ "${var.vnet_addr_pre01}${var.sn_addr_suf02}" ]
}

# ------------------------------------------
# Create Virtual Network Gateway
# ------------------------------------------

resource "azurerm_public_ip" "pip-vgw" {
  name                = "pip-vgw01"
  location            = azurerm_resource_group.rg.location
  resource_group_name = azurerm_resource_group.rg.name

  allocation_method = "Dynamic"
}

resource "azurerm_virtual_network_gateway" "vgw01" {
  name                = "vgw01"
  location            = azurerm_resource_group.rg.location
  resource_group_name = azurerm_resource_group.rg.name

  type     = "Vpn"
  vpn_type = "RouteBased"

  active_active = false
  enable_bgp    = false
  sku           = "Basic"

  ip_configuration {
    name                          = "vnetGatewayConfig"
    public_ip_address_id          = azurerm_public_ip.pip-vgw.id
    private_ip_address_allocation = "Dynamic"
    subnet_id                     = azurerm_subnet.GatewaySubnet.id
  }
}
```
はい！
**今回のメインディッシュ！！
<font color="Red">仮想ネットワークゲートウェイ！！！</font>**
でも全然簡単でした！
```type```が```Vpn```です。
これが```Vpn```じゃなくて```ExpressRoute```だとExpress Route用の仮想ネットワークゲートウェイを構築してくれます。
```vpn_type```の```RouteBase```は```PolicyBase```も指定できます。
しかしMicrosoftさんの推奨は```RouteBase```なので、おとなしく```RouteBase```にしておきましょう！
あとは```sku```が```Basic```くらいでしょうか。
ここで言及した```sku```以外のパラメーターは間違って構築してしまうと変更不可で、再構築が必要になりますので気を付けましょう。
もう一点注意点は、この仮想ネットワークゲートウェイ、構築完了するのに結構時間かかるんですよね。
待ちましょう。

## ローカルネットワークゲートウェイ構築
```localgateway.tf
# --------------------------------------
# Create Local Network Gateway
# --------------------------------------

resource "azurerm_local_network_gateway" "lgw" {
  name                = "${var.LG_name_pre}${var.LG_name_suf}"
  resource_group_name = azurerm_resource_group.rg.name
  location            = azurerm_resource_group.rg.location
  gateway_fqdn     = "${var.LG_DDNS}"
  address_space       = ["192.168.11.0/24","172.16.0.0/24"]
}
```
はい！
**今回の2皿目のメインディッシュ！！
<font color="Red">ローカルネットワークゲートウェイ！！！
もうおなか一杯！</font>**
さっきDDNSで接続してますと言うてましたが、それがこのコードの下から3行目、```gateway_fqdn```です。
固定IPアドレス(じゃなくて動的IPアドレスでも動作しますが)の場合は```gateway_address```です。
HashiCorpさんのサンプルコードには```gateway_address```しかなかったのでやり方がわかんなかったですが、適当に```gateway_fqdn```としてみたら動きました。
またHashiCorpさんのサンプルコードには```address_space```が1つしかなかったので我が家みたいに複数セグメントがある場合はどうしたらいいのかわからなかったのですが、適当に上述の通りに記載してみたら動作しました。
良かった良かった。

## 「接続」リソース構築
```vpn_connection.tf
resource "azurerm_virtual_network_gateway_connection" "vpn-connection" {
    name                       = "${var.connection_name_pre}${var.vnet_name_pre}${var.vnet_name_suf01}-${var.LG_name_pre}${var.LG_name_suf}"
    location                   = azurerm_resource_group.rg.location
    resource_group_name        = azurerm_resource_group.rg.name
    type                       = "IPsec"
    virtual_network_gateway_id = azurerm_virtual_network_gateway.vgw01.id
    local_network_gateway_id   = azurerm_local_network_gateway.lgw.id
    shared_key                 = "P@ssw0rd0123" #-Provided at run time
}
```
これは瞬殺かと思いきや一番時間がかかりました。
全然うまいこと動きませんでしたね。
サンプルコードがなかなか見つからなかったです。
## 実行結果
```
terraform appy
```
からの
```
yes
```
で、しばらく待ったら
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/643392/2d76e0ef-5bec-24d3-2282-1f8420f2109c.png)
こうなっておしまい！

あ、もう日付が変わってる！

いや、本記事の最初の方はめっちゃグダグダ言ってますけど、**<font color="Red">VPN張るところまで全部コードで作成できるって素晴らしいですね！</font>**

本日はここまで！