Azure NSGの疎通確認を一括で実行する


# はじめに
AzureでNetwork Security Group（NSG）を作成したものの疎通確認を1個1個実施していくと膨大な時間と手間が掛かってしまいます。
なんとか一括で出来ないものかと悩んでいたところ、
Network Watcherの`IPフローの確認`でNSGのチェックが出来るということを思い出しました。
そしてポータルにあるのであればAzure PowerShellで実行出来るだろう。と思い、調べてスクリプト作ってみました。

**参考：Network Watcher IPフローの確認画面**
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/604991/384db832-5971-5fc8-cd03-1b21f366d48d.png" width=70%>

# 疎通確認用インプットデータ準備
編集が簡単に出来るようにインプットデータはエクセルを選択。
エクセルに必要なデータを入力出来る表を作成。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/604991/6182992b-795e-639f-1fdb-99be6cfbd023.png" width = 90%>

# パラメータファイル
読込むエクセルのシート名や列などをパラメータ化し、jsonに記述するようにしました。
その他にサブスクリプションID等も一緒にパラメータ化。
シートは複数選択出来るようにしています。

```json:パラメータ
{
    "Excel": {
        "InitialDirectory": "C:\\",
        "Sheet": [
            {
                "Name": "Main",
                "Column": {
                    "VMName": 2,
                    "ResourceGroup": 3,
                    "Direction": 4,
                    "Protocol": 5,
                    "LocalPort": 6,
                    "RemoteIPAddress": 7,
                    "RemotePort": 8,
                    "Max": 8
                },
                "Row": {
                    "Start": 4
                }
            }
        ]
    },
    "Azure": {
        "SubscriptionID": "",
        "NetworkWatcher": {
            "ResourceGroup": "NetworkWatcherRG",
            "Name": "NetworkWatcher_japaneast"
        }
    },
    "Output": {
        "Directory": "C:\\out"
    }
}
```

# スクリプトの作成
PowerShell 7で作成しました。
エクセルを1行ずつ読み込んで`IPフローの確認`を実行します。
VMが停止していると実行できないため、停止している場合はスキップするようにしています。
他にはブランクの項目がある場合もスキップします。

```powershell:AzureNSGCheck.ps1（疎通確認実行部分を抜粋）
    foreach ($eachRow in $($Row.Start)..($MaxRow)) {
        # ブランク項目存在チェック
        $blankCol = $ExcelRange[$eachRow, $Column.VMName]
        $blankCol = $blankCol -and $ExcelRange[$eachRow, $Column.ResourceGroup]
        $blankCol = $blankCol -and $ExcelRange[$eachRow, $Column.Direction]
        $blankCol = $blankCol -and $ExcelRange[$eachRow, $Column.Protocol]
        $blankCol = $blankCol -and $ExcelRange[$eachRow, $Column.LocalPort]
        $blankCol = $blankCol -and $ExcelRange[$eachRow, $Column.RemoteIPAddress]
        $blankCol = $blankCol -and $ExcelRange[$eachRow, $Column.RemotePort]

        if (!$blankCol) {
            $Log.Warn("空白の項目が存在するため、次の行にスキップします。")
            continue
        }
        
        # VM情報の取得
        if ($VirtualMachine.Name -ne $ExcelRange[$eachRow, $Column.VMName]) {
            $VirtualMachine = $null
            $VirtualMachine = Get-AzVM -ResourceGroupName $ExcelRange[$eachRow, $Column.ResourceGroup] -Name $ExcelRange[$eachRow, $Column.VMName]

            if (!$VirtualMachine) {
                $Log.warn("仮想マシン「$($ExcelRange[$eachRow, $Column.VMName])」の取得に失敗しました。")
                $resultCheck = $returnCode.Error
                continue
            }

            # VMステータス取得
            $VMStatus = ($VirtualMachine | Get-AzVM -Status).Statuses | where { $_.Code -match "PowerState" }
        }

        if ($VMStatus.Code -ne "PowerState/running") {
            $Log.Warn("仮想マシン「$($ExcelRange[$eachRow, $Column.VMName])」を起動してください。現在のステータスは「$($VMStatus.Code)」です。")
            $resultCheck = $returnCode.Error
            continue
        }

        # プライベートIPアドレス（NIC）情報の取得
        $NetworkInterface = $null
        $NetworkInterface = Get-AzNetworkInterface -ResourceGroupName $VirtualMachine.ResourceGroupName | where { $_.VirtualMachine.Id -eq $VirtualMachine.Id }
        $LocalIP = $NetworkInterface.IpConfigurations.PrivateIpAddress
        
        $Log.info("測定内容： 仮想マシン：$($VirtualMachine.Name) 方向：$($ExcelRange[$eachRow, $Column.Direction]) プロトコル：$($ExcelRange[$eachRow, $Column.Protocol]) ローカルIP：${LocalIP} ローカルポート：$($ExcelRange[$eachRow, $Column.LocalPort]) リモートIP：$($ExcelRange[$eachRow, $Column.RemoteIPAddress]) リモートポート：$($ExcelRange[$eachRow, $Column.RemotePort])")

        # ローカルIPとリモートIPの同一チェック
        if ($LocalIP -eq $ExcelRange[$eachRow, $Column.RemoteIPAddress]) {
            $Log.Warn("リモートIPがローカルIPと同一のため、次の行にスキップします。")
            $resultCheck = $returnCode.Error
            continue
        }

        # IPFlow実行
        $resultNWIPFlow = $null
        $resultNWIPFlow = Test-AzNetworkWatcherIPFlow -NetworkWatcher $NetworkWatcher -TargetVirtualMachineId $VirtualMachine.Id -Direction $ExcelRange[$eachRow, $Column.Direction] -Protocol $ExcelRange[$eachRow, $Column.Protocol] -LocalIPAddress $LocalIP -RemoteIPAddress $ExcelRange[$eachRow, $Column.RemoteIPAddress] -LocalPort $ExcelRange[$eachRow, $Column.LocalPort] -RemotePort $ExcelRange[$eachRow, $Column.RemotePort]
        
        if (!$resultNWIPFlow) {
            $Log.Error("NSGチェック処理に失敗しました。")
            $resultCheck = $returnCode.Error
            continue
        }

        $Log.Info("結果： アクセス：$($resultNWIPFlow.Access) ルール：$($resultNWIPFlow.RuleName)")
        $OutputNSGCheck += "$($resultNWIPFlow.Access),$($resultNWIPFlow.RuleName),$($VirtualMachine.Name),$($ExcelRange[$eachRow, $Column.Direction]),$($ExcelRange[$eachRow, $Column.Protocol]),$($LocalIP),$($ExcelRange[$eachRow, $Column.LocalPort]),$($ExcelRange[$eachRow, $Column.RemoteIPAddress]),$($ExcelRange[$eachRow, $Column.RemotePort])"

    }
```

# スクリプトの実行
スクリプト実行前にAzureにPowerShellでログインします。

```powershell
Login-AzAccount
```

ログイン後、スクリプトを実行します。

```powershell
.\AzureNSGCheck.ps1 -ConfigFileName NSGCheckConfig.json
```

読込むエクセルファイルを選択するダイアログが表示されますのでファイルを選択します。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/604991/449572fe-f9c7-7704-1aa4-c4fffea53c40.png" width=90%>

処理が実行され、結果がcsvファイルで出力されます。
内容は許可/拒否、対応したセキュリティルール名と実行した情報です。

```plaintext:実行結果
Access,Rule,VMName,Direction,Protocol,LocalIP,LocalPort,RemoteIP,RemotePort
Allow,defaultSecurityRules/AllowVnetInBound,sample-vm,Inbound,TCP,10.0.0.4,3389,10.0.1.4,3389
Deny,defaultSecurityRules/DenyAllInBound,sample-vm,Inbound,TCP,10.0.0.4,3389,10.10.0.5,3389
Allow,defaultSecurityRules/AllowVnetInBound,sample-vm,Inbound,TCP,10.0.0.4,22,10.0.1.4,22
Deny,defaultSecurityRules/DenyAllOutBound,sample-vm,Outbound,TCP,10.0.0.4,443,0.0.0.0,443
```

# おわりに
これでAzureポータルからポチポチ実行しなくても良くなりました。
ただ、あくまでNSGのチェックになるのでスクリプトの実行結果がOKでもOS上で疎通確認すると結果が異なることがあります。
OS上のfirewall等で引っかかったり。
その辺りは不便ですが、NSG自体の設定に不備がないかの確認が出来るだけでも良しとします。
今回作成したスクリプトはGitHubに公開しています。（[こちら](https://github.com/ytamu2/Azure-NSGChecker)）