Amazon InspectorとMicrosoft Defender for Cloudの比較③脆弱な環境準備Azure編

1本目の記事はこちら。
[Amazon InspectorとMicrosoft Defender for Cloudの比較①脆弱な環境準備AWS編](https://qiita.com/hidekko/items/cd22d675323f054068d7)
2本目の記事はこちら。
[Amazon InspectorとMicrosoft Defender for Cloudの比較②Amazon Inspector有効化と分析結果編](https://qiita.com/hidekko/items/cd22d675323f054068d7)

はい。
さっきまで[2本目の記事](https://qiita.com/hidekko/private/10d39b8aa26e13cbd4c8)書いてました。
Amazon Inspector有効化して検査にしばらく時間がかかるかもしれないのでとりあえず24時間放置します。
その間にAzureでも同じ環境準備します。
現在12月3日10時37分です。

この記事はちょっと時間かかりそうですね。
以前AzureでもTerraformで環境構築自動化をLinuxとWindowsの2パターン作ってみたんですが、ちゃんと動いたんですがどうやったたすっかり忘れてしまっているからです。
頑張って書いていきましょう。

現在11時1分、30分くらいかけて過去自分で作ったTerraform見直してました。
ちゃんとデプロイできたはずなので、その前提で見直しました。
OSがUbuntu22.04 LTSだったので、この点AWSと差分が出ると良くないのでAlmaLinux9.x系にしてデプロイできるか試してみます。
かなり手前からのスタートだな・・・

現在12月3日21時、AlmaLinux9.xにしてAzure上でデプロイ、ヒアドキュメントで色々仕込むところまで含めて丸一日使っちゃいましたね。
途中で買い物行ったり家事やったりしてたんでそりゃ一日かかるわな、って感じですが、Defender for Cloud の動きまで見れたので良しとしましょう。

# 脆弱性のある環境の準備
はい。
では早速いっちゃいましょう。
今回もTerraform大活躍です。
何回でも言います。
**<font color="Red">Terraform、めっちゃ便利です！</font>**

# 脆弱性のある環境の構成図
はい。
前回の構成図は若干手抜き感があったのでもうちょっと作りこんでみました。
比較のためにまずは前回の構成図です。
![構成図_Azure.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/643392/e02397b1-583c-5e63-9b55-bc628b117991.png)
今回の構成図はこちらです。
![構成図_Azure_02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/643392/13244fed-34f2-2385-77cf-0aaad874836b.png)
WebサーバとDBサーバとでサブネットを分けました。
**「え？それだけ？」**
とお思いの皆様、これがまぁ大きな差なんですよ。
午前中の1~2時間くらい使ってちょっと構成を考えてみました。
元々流用しようと思っていたTerraformにNAT Gatewayがあったからややこしかったというのもあるのですが、**<font color="Red">AzureでAWSと同じような構成を取ろうとした場合、少なくとも今回の構成ではNAT Gatewayは不要です。</font>**
AWSの構成図も載せておきましょう。
![構成図_AWS.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/643392/c4f7ad15-1a6a-ba81-d1ad-fd7464342e1c.png)
やっぱり複雑ですね。
前回の記事では私もAzureは手抜き感が否めないと言いましたが、実はそうでもなくて、実際Azureの方がシンプルです。
ちょっと今回の本題からそれてしまうのですが、大事な部分なのでちゃんと説明します。

### AWSとAzureのネットワークに関する考え方の違い
私はAzureからPublic Cloudを触りだしたのでAzureの考え方がベースになっており、AWSに関してはAzureから意識的無意識的に関わらず、多少なりともコンバートして理解している部分があります。
CiscoのConfigでネットワークを理解している人がYAMAHAのConfig見て、構成図を描くために一度CiscoのConfigに意識的に無意識的にコンバートしているのとよく似ています。
プログラマーの方々も元々Cが得意な方はPython触るときCにコンバートしてませんかね？
あれと同じです。
ネットワーク構成という括りの中でAWSの場合、インターネットゲートウェイという考え方がAzureとの大きな違いで、<font color="Red">**AWSは明示的にインターネットゲートウェイを構築してVPCに接続しないとEC2はインターネットに出れない、AzureはVMを構築した段階で暗黙的にインターネットに接続されている**</font>という大きな違いがあります。
インターネットゲートウェイとは何ぞや？というのを理解するのにしばらく時間がかかりましたが、理解する手助けになったのは、これもAWSで良く採用されるネットワーク構成、**パブリックサブネット**と**プライベートサブネット**という考え方でした。
「何がプライベートで何がパブリックやねん？」と思ってたんですが、**プライベートサブネットはインターネットに直接接続されておらず、パブリックサブネットはインターネットに直接接続されているサブネットです。
これはサブネット内にあるEC2を基準に考えてインバウンド方向かアウトバウンド方向かに関わらず、インターネットに接続されているか否かのみが判断基準の様です。**
今回の構成図であればMySQLのインストールされているDBサーバはインターネットにNAT Gateway経由でアクセスしています。
ですのでこのサブネットはプライベートサブネットになります。
対するApacheのインストールされているWebサーバはインターネットゲートウェイ経由ですが、自身に割り振られたElastic IP Addressでインターネットに接続されている、直接インターネットに接続されているためパブリックサブネットとなります。
Azureではこのような概念は無くは無いのですが、AzureのVMはAWSでいうところのElastic IP Address、AzureではPublic IP Addressと言いますが、このPublic IP Addressが割り振られていなくてもインターネット接続できます。
AzureにはPublic IP Addressが振られていないVMがインターネットに出る際に利用される暗黙的に用意されている(利用料もかからない)NAT GatewayとPublic IP Addressが存在します。
他のサブスクリプション、ようは他のAzureユーザーと共用する可能性のあるPublic IP Addressです。
しかしこれがあるためにAzureではある意味プライベートサブネット(というより閉域という表現の方が近いでしょうか？)を構築しにくく、NSGでインバウンド、アウトバウンド共にAny Any Denyで止めてしまわなければなりません。(厳密にはAzure基盤とVM間の通信を遮ってしまうとVMが動作しなくなるため厳密な意味での閉域網は構築できません。これはAWSも同じですが。)
Azureに触り慣れているとこのことを感覚的にというか、無意識で当たり前と認識しているため、AWSのようにわざわざインターネットゲートウェイなるものを明示的にVPCに割り当てることを理解することが難しかったです。
またAWSのこの明示的にインターネットに接続しなければならない(インターネットゲートウェイをVPCに割り当てる)ためパブリックサブネットとプライベートサブネットを分けざるを得ない、という状況が発生します。
このインターネットゲートウェイにルートテーブルでデフォルトルートの宛先を向けてあげる必要があるのですが、このインターネットゲートウェイにデフォルトルートが向いているサブネットをパブリックサブネットと定義せざるを得ません。
なのでNAT GatewayがないとDBサーバはパッチ適用は愚か、My SQLのダウンロードもできません。

#### AWSとAzureのネットワークに関する考えたかの違いがSI及びユーザに及ぼす影響
一つは構築の複雑さです。
これはSIに直接関係します。
もう一点大事な部分があり、それはコストです。
NAT GatewayはMy SQL(今回はMy SQLですが、当然他のアプリケーションも同じ)をインストールしようとするとAWSのネットワーク概念から考えると必須、言い換えればAWSの仕様です。
そして言い方は悪いかもしれませんが、**ユーザはこのAWSの仕様のせいで、<font color="Red">Azureであれば払わなくても良いNAT Gatewayの費用を負担しなければなりません。</font>**
ﾋｮｴｰって感じですよね。
まぁAzureでもそういうことはあるんですけどね。
今回はたまたまこういう結果になりました。
最終的にまとめの記事も書きますが、これはAWSの大きな欠点だと考えています。
「そんなのIaaS使うからやないか。PaaS使ったらええんやないか？」
という意見も聞こえてきそうですが、「それ」と「これ」とは話が別です。
「これ」とは、NAT GatewayがAWSの仕様上必須であるにもかかわらず、その費用負担をユーザが強いられているという事実です。
別にAWSが悪いというわけでは無く、こういう設計ミスというか、考慮が足りない部分をなんとかうまいことしれくれませんかねーっていうのはSIやってて結構思います。
Azureに対してももちろん思うことはあるので、それはまた機会があれば記事にします。

もう一点、大きな違いがありました。
**Azureは何にもしなくてもLinux VMへのコンソール接続がAzure Portal上から可能です。
AWSはわざわざSSMの設定を行い、VPC EndpointにSSM用のエンドポイントを3つも構築し、しかもそれぞれのVPC Endpointに利用料金がかかります。**
これもAWSのプライベートサブネット、パブリックサブネットという概念(というか仕様)の影響でしょうね。

だいぶん脱線しましたが、話を元に戻してTerraformでAzure上に環境を構築していきましょう。

### Azure環境構築
ここからは1本目の記事と同じくコードが続きます。
まずは変数設定から。
```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     = "hackable-web-site"
}

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

# 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     = "vn-"
}

# vnet ip address prefix
variable "vnet_addr_pre" {
  type        = string
  default     = "10.0."
}

# vnet ip address suffix
variable "vnet_addr_suf" {
  type        = string
  default     = "0.0/23"
}

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

# sn for web name suffix
variable "sn_name_suf01" {
  type        = string
  default     = "web-server01"
}

# sn for web ip address suffix
variable "sn_addr_suf01" {
  type        = string
  default     = "0.0/24"
}

# sn for db name suffix
variable "sn_name_suf02" {
  type        = string
  default     = "db-server01"
}

# sn for db ip address suffix
variable "sn_addr_suf02" {
  type        = string
  default     = "1.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
}

# NAT Gateway name prefix
variable "ng_name_pre" {
  type        = string
  default     = "ng-"
}

# NAT Gateway name suffix
variable "ng_name_suf" {
  type        = string
  default     = "01"
}

# 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_DS1_v2"
}

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

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

# vm name suffix02
variable "vm_name_suf02" {
  type        = string
  default     = "db01"
}

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

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

# 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_sku" {
  type        = string
  default     = "9-gen1"
}

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

AWSとIPアドレッシングもOSも合わせています。
変数の最初の方でサブスクリプションIDやらクライアントID、クライアントシークレットやらを呼んでるのはOSの変数から呼んでます。
なので事前に変数にそれぞれの値を格納してもらえたらと思います。
他の方法でも全然OKです。

今回はAlmaLinux9.x系を使っていますが、他のOSの指定の仕方もコメントに書いておいたのでよかったら参考にしてください。
WindowsはそもそもVM作成のtfファイルでのコードが違うので、今回のtfファイルをそのまま流用してWindowsマシンをAzure上に立てることはできませんのであしからず。

続いてterraformのバージョンやプロバイダバージョンを指定するprividers.tfです。
```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}"
}

```
次にリソースグループ設定です。
```resource_group.tf
# rg Create
resource "azurerm_resource_group" "rg" {
  location = var.region
  name     = "${var.rg_name_pre}${var.system_name}"
}
```

続いて仮想ネットワーク(Vnet)の設定です。
```vnet_subnet.tf
# vnet Create
resource "azurerm_virtual_network" "vnet" {
  resource_group_name = azurerm_resource_group.rg.name
  location = azurerm_resource_group.rg.location
  name     = "${var.vnet_name_pre}${var.system_name}"
  address_space = [ "${var.vnet_addr_pre}${var.vnet_addr_suf}" ]
}

# sn-web-server01 Create
resource "azurerm_subnet" "sn-web-server01" {
  resource_group_name = azurerm_resource_group.rg.name
  name = "${var.sn_name_pre}${var.sn_name_suf01}"
  virtual_network_name = azurerm_virtual_network.vnet.name
  address_prefixes = [ "${var.vnet_addr_pre}${var.sn_addr_suf01}" ]
  service_endpoints = [ "Microsoft.Storage" ]
}

# sn-db-server01 Create
resource "azurerm_subnet" "sn-db-server01" {
  resource_group_name = azurerm_resource_group.rg.name
  name = "${var.sn_name_pre}${var.sn_name_suf02}"
  virtual_network_name = azurerm_virtual_network.vnet.name
  address_prefixes = [ "${var.vnet_addr_pre}${var.sn_addr_suf02}" ]
  service_endpoints = [ "Microsoft.Storage" ]
}
```
ここはめっちゃシンプルですね。
Vnet1つとその中にサブネット2つ作ってるだけです。
続いて<font color="Red">NSG</font>(<font color="Red">N</font>etwork <font color="Red">S</font>ecurity <font color="Red">G</font>roup)、AWSでいうところのSecurity Groupです。
```nsg.tf
# nsg-sn-web-server01 Create
resource "azurerm_network_security_group" "nsg-sn-web-server01" {
  resource_group_name = azurerm_resource_group.rg.name
  location = azurerm_resource_group.rg.location
  name = "${var.nsg_name_pre}${azurerm_subnet.sn-web-server01.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 = "22"
    source_address_prefix = "ご自身のグローバルIPアドレス"
    destination_address_prefix = "*"
  }

  security_rule {
    name = "AllowInBoundHTTPOnPremises"
    priority = 1002
    direction = "Inbound"
    access = "Allow"
    protocol = "Tcp"
    source_port_range = "*"
    destination_port_range = "80"
    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 = "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-web-server01 to sn-web-server01
resource "azurerm_subnet_network_security_group_association" "nsg-sn-web-server01-to-sn-web-server01" {
  subnet_id =  azurerm_subnet.sn-web-server01.id
  network_security_group_id = azurerm_network_security_group.nsg-sn-web-server01.id
}

# nsg-sn-db-server01 Create
resource "azurerm_network_security_group" "nsg-sn-db-server01" {
  resource_group_name = azurerm_resource_group.rg.name
  location = azurerm_resource_group.rg.location
  name = "${var.nsg_name_pre}${azurerm_subnet.sn-db-server01.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 = "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 = "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-db-server01 to sn-db-server01
resource "azurerm_subnet_network_security_group_association" "nsg-sn-db-server01-to-sn-db-server01" {
  subnet_id =  azurerm_subnet.sn-db-server01.id
  network_security_group_id = azurerm_network_security_group.nsg-sn-db-server01.id
}
```
app01のNSGにはインバウンド方向でTCP80番ポートとTCP22番をご自身のグローバルIPアドレスから許可してあげてください。
あと大きなポイント、先ほどもちょっと触れましたがAzure VMはAzure基盤との通信を確保しないとVMが動作しなくなりますので、そのための設定をインバウンド、アウトバウンドそれぞれ100や101など、最上位の優先順位で通信を確保しています。
必ずAzure基盤との通信は確保されるように考慮しての設計です。

はい。
これで枠が整いましたね。
それではLinux VMの構築です。
```linux_vm.tf
# pip-vm-app01 Create
resource "azurerm_public_ip" "pip-vm-app01" {
  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"
}

# nic-vm-app01 Create
resource "azurerm_network_interface" "nic-vm-app01" {
  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-web-server01.id
    private_ip_address_allocation = "${var.priv_ip_alloc}"
    private_ip_address = "10.0.0.11"
    public_ip_address_id = azurerm_public_ip.pip-vm-app01.id
  }
}

# linux vm01 Create
resource "azurerm_linux_virtual_machine" "vm-app01" {
  name                  = "${var.vm_name_pre}${var.vm_name_suf01}"
  admin_username        = "${var.adminuser}"
  admin_password        = "P@ssw0rd0123"
  disable_password_authentication = false
  location              = azurerm_resource_group.rg.location
  resource_group_name   = azurerm_resource_group.rg.name
  network_interface_ids = [azurerm_network_interface.nic-vm-app01.id]
  size                  = "${var.vm_sku}"
  
  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_pub}"
    offer     = "${var.os_offer}"
    sku       = "${var.os_sku}"
    version   = "${var.os_ver}"
  }

  custom_data = "IyEvYmluL2Jhc2gNCnN1ZG8gZG5mIHVwZGF0ZSAteQ0Kc3VkbyBkbmYgaW5zdGFsbCAteSBodHRwZCBwaHAgcGhwLW15c3FsaQ0Kc3VkbyBzeXN0ZW1jdGwgc3RhcnQgaHR0cGQNCnN1ZG8gc3lzdGVtY3RsIGVuYWJsZSBodHRwZA0Kc3VkbyB1c2VybW9kIC1hIC1HIGFwYWNoZSAke3Zhci5hZG1pbnVzZXJ9DQpzdWRvIGNob3duIC1SICR7dmFyLmFkbWludXNlcn06YXBhY2hlIC92YXIvd3d3DQpzdWRvIGNobW9kIDI3NzUgL3Zhci93d3cNCmZpbmQgL3Zhci93d3cgLXR5cGUgZCAtZXhlYyBzdWRvIGNobW9kIDI3NzUge30gXDsNCmZpbmQgL3Zhci93d3cgLXR5cGUgZiAtZXhlYyBzdWRvIGNobW9kIDA2NjQge30gXDsNCmVjaG8gIjw/cGhwIHBocGluZm8oKTsgPz4iID4gL3Zhci93d3cvaHRtbC9waHBpbmZvLnBocA0Kc3VkbyBmaXJld2FsbC1jbWQgLS1saXN0LWFsbCAtLXpvbmU9cHVibGljIC0tcGVybWFuZW50DQpzdWRvIGZpcmV3YWxsLWNtZCAtLXJlbW92ZS1zZXJ2aWNlPWNvY2twaXQgLS16b25lPXB1YmxpYyAtLXBlcm1hbmVudA0Kc3VkbyBmaXJld2FsbC1jbWQgLS1saXN0LWFsbCAtLXpvbmU9cHVibGljIC0tcGVybWFuZW50DQpzdWRvIGZpcmV3YWxsLWNtZCAtLWFkZC1zZXJ2aWNlPWh0dHAgLS16b25lPXB1YmxpYyAtLXBlcm1hbmVudA0Kc3VkbyBmaXJld2FsbC1jbWQgLS1saXN0LWFsbCAtLXpvbmU9cHVibGljIC0tcGVybWFuZW50DQpzdWRvIGZpcmV3YWxsLWNtZCAtLXJlbG9hZA0Kc3VkbyBzZXRzZWJvb2wgLVAgaHR0cGRfY2FuX25ldHdvcmtfY29ubmVjdCAx"

  computer_name  = "${var.vm_name_pre}${var.vm_name_suf01}"

  boot_diagnostics {
    storage_account_uri = azurerm_storage_account.storage_account.primary_blob_endpoint
  }
}

# nic-vm-db01 Create
resource "azurerm_network_interface" "nic-vm-db01" {
  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_suf02}"

  ip_configuration {
    name = "${var.nic_name_pre}${var.vm_name_pre}${var.vm_name_suf02}${var.ipconf_suf}"
    subnet_id = azurerm_subnet.sn-db-server01.id
    private_ip_address_allocation = "${var.priv_ip_alloc}"
    private_ip_address = "10.0.1.11"
#    public_ip_address_id = azurerm_public_ip.pip02.id
  }
}

# linux vm02 Create
resource "azurerm_linux_virtual_machine" "vm-db01" {
  name                  = "${var.vm_name_pre}${var.vm_name_suf02}"
  admin_username        = "${var.adminuser}"
  admin_password        = "P@ssw0rd0123"
  disable_password_authentication = false
  location              = azurerm_resource_group.rg.location
  resource_group_name   = azurerm_resource_group.rg.name
  network_interface_ids = [azurerm_network_interface.nic-vm-db01.id]
  size                  = "${var.vm_sku}"
  
  os_disk {
    name                 = "${var.vm_osdisk_pre}${var.vm_name_pre}${var.vm_name_suf02}"
    caching              = "ReadWrite"
    storage_account_type = "${var.vm_osdisk_sku}"
  }

  source_image_reference {
    publisher = "${var.os_pub}"
    offer     = "${var.os_offer}"
    sku       = "${var.os_sku}"
    version   = "${var.os_ver}"
  }

  custom_data = "IyEvYmluL2Jhc2gNCnN1ZG8gZG5mIHVwZGF0ZSAteQ0Kc3VkbyBkbmYgLXkgbG9jYWxpbnN0YWxsICBodHRwczovL2Rldi5teXNxbC5jb20vZ2V0L215c3FsODAtY29tbXVuaXR5LXJlbGVhc2UtZWw5LTEubm9hcmNoLnJwbQ0Kc3VkbyBkbmYgaW5zdGFsbCAteSBwaHAgbXlzcWwtc2VydmVyIHBocC1teXNxbG5kDQpzdWRvIHN5c3RlbWN0bCBzdGFydCBteXNxbGQNCnN1ZG8gc3lzdGVtY3RsIGVuYWJsZSBteXNxbGQNCnN1ZG8gZmlyZXdhbGwtY21kIC0tbGlzdC1hbGwgLS16b25lPXB1YmxpYyAtLXBlcm1hbmVudA0Kc3VkbyBmaXJld2FsbC1jbWQgLS1yZW1vdmUtc2VydmljZT1jb2NrcGl0IC0tem9uZT1wdWJsaWMgLS1wZXJtYW5lbnQNCnN1ZG8gZmlyZXdhbGwtY21kIC0tbGlzdC1hbGwgLS16b25lPXB1YmxpYyAtLXBlcm1hbmVudA0Kc3VkbyBmaXJld2FsbC1jbWQgLS1hZGQtc2VydmljZT1teXNxbCAtLXpvbmU9cHVibGljIC0tcGVybWFuZW50DQpzdWRvIGZpcmV3YWxsLWNtZCAtLWxpc3QtYWxsIC0tem9uZT1wdWJsaWMgLS1wZXJtYW5lbnQNCnN1ZG8gZmlyZXdhbGwtY21kIC0tcmVsb2FkDQo="

  computer_name  = "${var.vm_name_pre}${var.vm_name_suf02}"

  boot_diagnostics {
    storage_account_uri = azurerm_storage_account.storage_account.primary_blob_endpoint
  }
}
```
ここでのポイントはヒアドキュメントがbase64でエンコード化されているということです。
AWSではそんなことする必要がなく、過去のドキュメントを参照していると平文でもAzureで動いていそうなのですが、私の環境ではbase64でエンコードしなければ動きませんでした。
「おま環」の可能性が捨てきれませんが、Webサーバでは以下のコマンドを叩いています。
```vm-app01のヒアドキュメントの平文版.sh
#!/bin/bash
sudo dnf update -y
sudo dnf install -y httpd php php-mysqli
sudo systemctl start httpd
sudo systemctl enable httpd
sudo usermod -a -G apache ${var.adminuser}
sudo chown -R ${var.adminuser}:apache /var/www
sudo chmod 2775 /var/www
find /var/www -type d -exec sudo chmod 2775 {} \;
find /var/www -type f -exec sudo chmod 0664 {} \;
echo "<?php phpinfo(); ?>" > /var/www/html/phpinfo.php
sudo firewall-cmd --list-all --zone=public --permanent
sudo firewall-cmd --remove-service=cockpit --zone=public --permanent
sudo firewall-cmd --list-all --zone=public --permanent
sudo firewall-cmd --add-service=http --zone=public --permanent
sudo firewall-cmd --list-all --zone=public --permanent
sudo firewall-cmd --reload
sudo setsebool -P httpd_can_network_connect 1
```
今回(やめておけばいいのに)SE Linux有効化状態で動作させています。
DBサーバのヒアドキュメントの平文版はこちらです。
```DBサーバのヒアドキュメントの平文版.sh
#!/bin/bash
sudo dnf update -y
sudo dnf -y localinstall  https://dev.mysql.com/get/mysql80-community-release-el9-1.noarch.rpm
sudo dnf install -y php mysql-server php-mysqlnd
sudo systemctl start mysqld
sudo systemctl enable mysqld
sudo firewall-cmd --list-all --zone=public --permanent
sudo firewall-cmd --remove-service=cockpit --zone=public --permanent
sudo firewall-cmd --list-all --zone=public --permanent
sudo firewall-cmd --add-service=mysql --zone=public --permanent
sudo firewall-cmd --list-all --zone=public --permanent
sudo firewall-cmd --reload
```
もう見てもらったらそのまんまですね。
大したことはしていません。

最後にVMの診断情報を出力するストレージアカウントを作成します。
```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-db-server01.id,azurerm_subnet.sn-web-server01.id]
  ip_rules = ["ご自身のグローバルIPアドレス"]
}
```
これもまぁ普通ですかね。
一点あるとすれば、AWSのS3と同じくストレージアカウントもインターネット上で一意(ユニーク)な名前でないとデプロイできませんので、ランダム関数を使っています。
このランダム関数も変数の方で宣言して持ってきてます。
(ランダム関数使ってもインターネット上で一意になるとは限りませんが、まぁ毎回自力で考えるよりいいかな、という程度です。)

はい。
後の手順、これらのtfファイルの配置はterraform init、terraform applyなどの手順は全部AWSの記事と同じです！

更にその後のWebサーバへの仕込みやDBサーバへの仕込みも全部同じです！
現在23時21分、もう勘弁してください！
記事公開までに余力があればちゃんと(といってもAWSの記事の焼き直し)書きます！

本日はここまで！