Azure NSGの登録と出力を一括で行う

# はじめに
AzureのNetwork Security Group（NSG）の登録作業をAzureポータルで行うと手間が掛かり、ミスも起きる可能性があるため
一括で入出力が出来るようにします。

# 動作確認環境
PowerShell 7.1.3
Azure PowerShell 5.7.0

# 作成
## 設定ファイル
入出力ファイルのレイアウト、区切り文字等を変更出来るよう設定ファイルを用意しました。

```json:testsetting.json
{
    "Azure": {
        "SubscriptionID": "xxxxxx",
        "TenantID": "xxxxxx"
    },
    "Convert": {
        "EntryType": [
            {
                "Caption": "登録",
                "DeleteRule": false
            },
            {
                "Caption": "削除",
                "DeleteRule": true
            }
        ]
    },
    "Import": {
        "InitialDirectory": "C:\\",
        "csv": {
            "Delimiter": "\t"
        }
    },
    "Export": {
        "Directory": "",
        "csv": {
            "Delimiter": "\t"
        }
    },
    "Layout": [
        {
            "Title": "EntryType",
            "Resource": "EntryType",
            "in": 0,
            "out": -1,
            "NSGResource": false
        },
        {
            "Title": "NSGName",
            "Resource": "Name",
            "in": 1,
            "out": 0,
            "NSGResource": true
        },
        {
            "Title": "ResourceGroup",
            "Resource": "ResourceGroupName",
            "in": 2,
            "out": 1,
            "NSGResource": true
        },
        {
            "Title": "Direction",
            "Resource": "Direction",
            "in": 3,
            "out": 2,
            "NSGResource": false
        },
        {
            "Title": "Priority",
            "Resource": "Priority",
            "in": 4,
            "out": 3,
            "NSGResource": false
        },
        {
            "Title": "RuleName",
            "Resource": "Name",
            "in": 5,
            "out": 4,
            "NSGResource": false
        },
        {
            "Title": "SourceAddressPrefix",
            "Resource": "SourceAddressPrefix",
            "in": 6,
            "out": 5,
            "NSGResource": false
        },
        {
            "Title": "SourcePortRange",
            "Resource": "SourcePortRange",
            "in": 7,
            "out": 6,
            "NSGResource": false
        },
        {
            "Title": "DestinationAddressPrefix",
            "Resource": "DestinationAddressPrefix",
            "in": 8,
            "out": 7,
            "NSGResource": false
        },
        {
            "Title": "DestinationPortRange",
            "Resource": "DestinationPortRange",
            "in": 9,
            "out": 8,
            "NSGResource": false
        },
        {
            "Title": "Protocol",
            "Resource": "Protocol",
            "in": 10,
            "out": 9,
            "NSGResource": false
        },
        {
            "Title": "Access",
            "Resource": "Access",
            "in": 11,
            "out": 10,
            "NSGResource": false
        }
    ]
}
```

## 必ずエクスポートを実行
誤った内容を登録してしまうことも考慮し、インポート前に必ずエクスポートするようにしました。
これで間違って登録してしまっても修正が簡単に行えます。
エクスポート処理は設定ファイルで指定したヘッダ、区切り文字、列順で行います。
サブスクリプション内のNSGすべてを対象とし、NSG毎にファイル出力します。
以下のコードではLogの出力処理は別途作成しています。

```powershell
function exportNSG {
    param (
        $Settingjson,
        $Log,
        $returnCode
    )

    $Log.Info("NSGのエクスポートを開始します。")
    $TimeStamp = Get-Date -Format "yyyyMMddHHmmss"

    if (!(Test-Path -Path $Settingjson.Export.Directory)) {
        $Log.Error("出力ディレクトリ「$($Settingjson.Export.Directory)」が存在しません。")
        return $returnCode.Error
    }

    $Log.info("出力可能な全てのNSGを取得します。")
    $NSGALL = Get-AzNetworkSecurityGroup
    if (!$NSGALL) {
        $Log.Error("NSGの取得に失敗しました。")
        return $returnCode.Error
    }
    $Log.Info("NSGの取得に成功しました。")
    
    foreach ($NSG in $NSGALL) {
        $Log.Info("NSG「$($NSG.Name)」の定義出力を開始します。")

        $OutNSG = $null
        $OutLayoutjson = $Settingjson.Layout | where { $_.out -ge 0 } | sort out
        $OutNSG = $($OutLayoutjson.Title -join $Settingjson.Export.csv.Delimiter)

        foreach ($Rule in ($NSG.SecurityRules | sort Direction, Priority)) {
            $OutLine = @()
            foreach ($eachLayout in $OutLayoutjson) {
                $Column = $Rule
                if ($eachLayout.NSGResource) {
                    $Column = $NSG
                }

                $OutLine += $Column.$($eachLayout.Resource) -join ","
            }
            $OutNSG += "`n" + $($OutLine -join $Settingjson.Export.csv.Delimiter)

        }
        $OutFilePath = Join-Path $Settingjson.Export.Directory -ChildPath "$($NSG.Name)_${TimeStamp}_${PID}.csv"
        $OutNSG | Out-File -FilePath $OutFilePath -Encoding utf8
    
        $Log.Info("NSG「$($NSG.Name)」の定義出力が終了しました。")
    }

    $Log.Info("NSGのエクスポートが終了しました。")

    return $returnCode.Success
}
```

## インポート処理の動作
セキュリティルールの登録、更新、および削除は行うけど、NSGそのものを削除するような事は避けました。
誤ってバンバン削除してしまいそうなので…

# 実行
インポートするファイルの内容

```plaintext:sample.csv
EntryType	NSGName	ResourceGroup	Direction	Priority	RuleName	SourceAddressPrefix	SourcePortRange	DestinationAddressPrefix	DestinationPortRange	Protocol	Access
登録	sample-nsg	sample-rg	Inbound	100	AllowRDP	10.0.0.5	*	*	3389	TCP	Allow
#登録	sample-nsg	sample-rg	Inbound	300	AllowRDP3	10.0.0.5	*	*	3389	TCP	Allow
登録	sample2-nsg	sample-rg	Inbound	100	AllowRDP	10.0.0.5	*	*	3389	TCP	Allow
登録	sample-nsg	sample-rg	Inbound	200	AllowCIFS	10.0.0.5,10.0.1.5,10.0.1.6	*	*	445	TCP	Allow
登録	sample-nsg	sample-rg	outbound	200	AllowVnet	*	*	VirtualNetwork	80,443	*	Allow
登録	sample-nsg	sample-rg	Inbound	500	DenyRDP	10.0.0.5,10.1.0.5	3389	*	3389	*	Deny
登録	sample-nsg	sample-rg	outbound	100	DenyInternet	10.0.0.5,10.1.0.5,10.1.0.6	*	Internet	80,443	TCP	Deny
登録	sample-nsg	sample-rg	Inbound	600	AllowDNS	10.0.0.5	*	*	53	UDP	Allow
登録	sample-nsg	sample-rg	Inbound	700	AllowPing	10.0.0.5	*	10.1.0.0/24	*	ICMP	Allow
登録	sample-nsg	sample-rg	Inbound	800	AllowInternet	internet	443,80	internet	443,80	TCP	Allow
```

登録対象のNSGは存在しません。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/604991/5b8efdc0-1845-b8a6-5858-a356e4dff376.png" width=50%>

PowerShellを開いて実行します。

```powershell
.\AzureNSGRegister.ps1 -ConfigFileName testSetting.json
```

ファイルを開くダイアログが表示されますのでインポートファイルを選択します。
複数ファイルも選択可能です。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/604991/324f50f5-751a-7ad8-a28e-0d9b6a23ea06.png" width=80%>


ブラウザで認証画面が開きますので設定ファイルで指定したサブスクリプションが使用できるアカウントを選択してAzureへログインします。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/604991/6c6512ca-e908-dfad-65eb-e91e57aa5e06.png" width=50%>


処理が完了したのでログを確認します。

```plaintext:logファイル
[2021/04/06 23:24:03] INFO 設定ファイル「testsetting.json」
[2021/04/06 23:24:10] INFO NSGのエクスポートを開始します。
[2021/04/06 23:24:10] INFO 出力可能な全てのNSGを取得します。
[2021/04/06 23:24:11] INFO NSGの取得に成功しました。
[2021/04/06 23:24:11] INFO NSG「main-nsg」の定義出力を開始します。
[2021/04/06 23:24:11] INFO NSG「main-nsg」の定義出力が終了しました。
[2021/04/06 23:24:11] INFO NSG「sub-nsg」の定義出力を開始します。
[2021/04/06 23:24:11] INFO NSG「sub-nsg」の定義出力が終了しました。
[2021/04/06 23:24:11] INFO NSG「test2-nsg」の定義出力を開始します。
[2021/04/06 23:24:11] INFO NSG「test2-nsg」の定義出力が終了しました。
[2021/04/06 23:24:11] INFO NSG「test3-nsg」の定義出力を開始します。
[2021/04/06 23:24:11] INFO NSG「test3-nsg」の定義出力が終了しました。
[2021/04/06 23:24:11] INFO NSGのエクスポートが終了しました。
[2021/04/06 23:24:11] INFO ファイル「sample.csv」の読込を開始します。
[2021/04/06 23:24:11] INFO NSG「sample-nsg」のインポート処理を開始します。
[2021/04/06 23:24:13] INFO NSG「sample-nsg」を作成します。
[2021/04/06 23:24:17] INFO NSG「sample-nsg」の作成に成功しました。
[2021/04/06 23:24:18] INFO 受信セキュリティ規則「AllowRDP」の追加を開始します。
[2021/04/06 23:24:19] INFO 受信セキュリティ規則「AllowRDP」の追加に成功しました。
[2021/04/06 23:24:19] INFO 受信セキュリティ規則「AllowCIFS」の追加を開始します。
[2021/04/06 23:24:20] INFO 受信セキュリティ規則「AllowCIFS」の追加に成功しました。
[2021/04/06 23:24:20] INFO 送信セキュリティ規則「AllowVnet」の追加を開始します。
[2021/04/06 23:24:21] INFO 送信セキュリティ規則「AllowVnet」の追加に成功しました。
[2021/04/06 23:24:22] INFO 受信セキュリティ規則「DenyRDP」の追加を開始します。
[2021/04/06 23:24:22] INFO 受信セキュリティ規則「DenyRDP」の追加に成功しました。
[2021/04/06 23:24:23] INFO 送信セキュリティ規則「DenyInternet」の追加を開始します。
[2021/04/06 23:24:23] INFO 送信セキュリティ規則「DenyInternet」の追加に成功しました。
[2021/04/06 23:24:24] INFO 受信セキュリティ規則「AllowDNS」の追加を開始します。
[2021/04/06 23:24:25] INFO 受信セキュリティ規則「AllowDNS」の追加に成功しました。
[2021/04/06 23:24:25] INFO 受信セキュリティ規則「AllowPing」の追加を開始します。
[2021/04/06 23:24:26] INFO 受信セキュリティ規則「AllowPing」の追加に成功しました。
[2021/04/06 23:24:26] INFO 受信セキュリティ規則「AllowInternet」の追加を開始します。
[2021/04/06 23:24:27] INFO 受信セキュリティ規則「AllowInternet」の追加に成功しました。
[2021/04/06 23:24:27] INFO NSG「sample-nsg」のセキュリティ規則を更新します。
[2021/04/06 23:24:31] INFO NSG「sample-nsg」のセキュリティ規則更新に成功しました。
[2021/04/06 23:24:31] INFO NSG「sample-nsg」のインポート処理を終了します。
[2021/04/06 23:24:31] INFO NSG「sample2-nsg」のインポート処理を開始します。
[2021/04/06 23:24:32] INFO NSG「sample2-nsg」を作成します。
[2021/04/06 23:24:37] INFO NSG「sample2-nsg」の作成に成功しました。
[2021/04/06 23:24:37] INFO 受信セキュリティ規則「AllowRDP」の追加を開始します。
[2021/04/06 23:24:38] INFO 受信セキュリティ規則「AllowRDP」の追加に成功しました。
[2021/04/06 23:24:38] INFO NSG「sample2-nsg」のセキュリティ規則を更新します。
[2021/04/06 23:24:42] INFO NSG「sample2-nsg」のセキュリティ規則更新に成功しました。
[2021/04/06 23:24:42] INFO NSG「sample2-nsg」のインポート処理を終了します。
[2021/04/06 23:24:42] INFO ファイル「sample.csv」の読込が終了しました。
[2021/04/06 23:24:42] INFO 全ての処理が終了しました。
[2021/04/06 23:24:42] INFO Exit code: 0
```

Azureポータルで確認します。
無事NSGが作られていました。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/604991/4dfc3717-504e-1279-8c13-af22c132c8fe.png" width=80%>
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/604991/19b26d8f-dd40-ca59-0f0e-7ec251912133.png" width=95%>

NSGのエクスポートも確認出来ました。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/604991/15d6e938-9f4a-b269-971e-b3d1c1608561.png" width=95%>


# 終わりに
煩わしい手動での登録作業ともこれでおさらばです。
ただ、Application Security Groupに対応していない中途半端さです…

今回作成したスクリプトは[Github](https://github.com/ytamu2/Azure-NSGRegister)にて公開しています。