🧙‍♂️

[Azure]ファイアウォールポリシーのルール一覧をCSVで出力

2023/04/23に公開

Azure Firewallのポリシー変更がある度にパラメータシートを更新するというありがちな運用をしていまして、これが些末なことですが大変辛くなる作業です。

ARMテンプレートをそのままパラメータシートにすればいいやん!という単純なことに気づいたのですが、JSONのままだと文句を言われそうなのでARMテンプレートをCSVに加工してみました。

前提としてのAzure Firewallのポリシー構造について

Azure Firewallのポリシーはファイアウォールポリシーというリソースで管理されており、Azure Firewallにバインドすることでポリシーとして機能するという仕組みです。

ファイアウォールポリシーというリソースの構成要素は以下のようになっています。

  • ファイアウォールポリシー(リソース)
    • ルールコレクショングループ(優先度)
      • ルールコレクション(優先度/ルール種別/アクション)
        • DNATルール(IP/ポート/FQDNなどの通信要件)
        • ネットワークルール(同上)
        • アプリケーションルール(同上)

ルールコレクショングループとルールコレクションで二重に優先度を設定できますが、ルールコレクショングループの優先度はルール全体の処理順を決める役割がある一方、ルールコレクションの優先度はルールコレクショングループ内での処理順を決めるためのものです。[1]

ルールの種別によって入力フィールドも異なってくるので、DNAT/ネットワーク/アプリケーションの3つで別々にCSVを出力するような形にします。

まずはARMテンプレートの準備

CSVにしたいファイアウォールポリシーのARMテンプレートをエクスポートします。
azの場合標準出力で戻ってくるのでリダイレクトさせてエクスポートします。
↓はCloud Shell(Core版PowerShell)で実行する場合。

Azure CLI
az group export --name '<リソースグループ名>' --resource-ids '<リソースid>' > ./template.json
Azure PowerShell
Export-AzResourceGroup -ResourceGroupName '<リソースグループ名>' -Resource '<リソースid>' -Path './template.json'

Azure Portalからダウンロードする場合は[テンプレートのエクスポート]からダウンロードします。[パラメーターを含める]のチェックを入れてください。

今回用意したARMテンプレートはこちらです。

json(クリックして展開)
{
    "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
    "contentVersion": "1.0.0.0",
    "parameters": {
        "firewallPolicies_fwpol_test_name": {
            "defaultValue": "fwpol-test",
            "type": "String"
        }
    },
    "variables": {},
    "resources": [
        {
            "type": "Microsoft.Network/firewallPolicies",
            "apiVersion": "2022-09-01",
            "name": "[parameters('firewallPolicies_fwpol_test_name')]",
            "location": "japaneast",
            "properties": {
                "sku": {
                    "tier": "Basic"
                },
                "threatIntelMode": "Off",
                "threatIntelWhitelist": {
                    "fqdns": [],
                    "ipAddresses": []
                }
            }
        },
        {
            "type": "Microsoft.Network/firewallPolicies/ruleCollectionGroups",
            "apiVersion": "2022-09-01",
            "name": "[concat(parameters('firewallPolicies_fwpol_test_name'), '/rulecolgroup-1')]",
            "location": "japaneast",
            "dependsOn": [
                "[resourceId('Microsoft.Network/firewallPolicies', parameters('firewallPolicies_fwpol_test_name'))]"
            ],
            "properties": {
                "priority": 10000,
                "ruleCollections": [
                    {
                        "ruleCollectionType": "FirewallPolicyFilterRuleCollection",
                        "action": {
                            "type": "Allow"
                        },
                        "rules": [
                            {
                                "ruleType": "NetworkRule",
                                "name": "dns",
                                "ipProtocols": [
                                    "UDP"
                                ],
                                "sourceAddresses": [
                                    "*"
                                ],
                                "sourceIpGroups": [],
                                "destinationAddresses": [
                                    "8.8.8.8"
                                ],
                                "destinationIpGroups": [],
                                "destinationFqdns": [],
                                "destinationPorts": [
                                    "53"
                                ]
                            }
                        ],
                        "name": "rulecol-net-1",
                        "priority": 200
                    },
                    {
                        "ruleCollectionType": "FirewallPolicyNatRuleCollection",
                        "action": {
                            "type": "Dnat"
                        },
                        "rules": [
                            {
                                "ruleType": "NatRule",
                                "name": "dnatrule",
                                "translatedAddress": "10.0.0.2",
                                "translatedPort": "80",
                                "ipProtocols": [
                                    "TCP"
                                ],
                                "sourceAddresses": [
                                    "*"
                                ],
                                "sourceIpGroups": [],
                                "destinationAddresses": [
                                    "10.0.0.1"
                                ],
                                "destinationPorts": [
                                    "8080"
                                ]
                            }
                        ],
                        "name": "rulecol-dnat-1",
                        "priority": 100
                    },
                    {
                        "ruleCollectionType": "FirewallPolicyFilterRuleCollection",
                        "action": {
                            "type": "Allow"
                        },
                        "rules": [
                            {
                                "ruleType": "ApplicationRule",
                                "name": "yahoo",
                                "protocols": [
                                    {
                                        "protocolType": "Https",
                                        "port": 443
                                    }
                                ],
                                "fqdnTags": [],
                                "webCategories": [],
                                "targetFqdns": [
                                    "yahoo.co.jp"
                                ],
                                "targetUrls": [],
                                "terminateTLS": false,
                                "sourceAddresses": [
                                    "*"
                                ],
                                "destinationAddresses": [],
                                "sourceIpGroups": []
                            }
                        ],
                        "name": "rulecol-app-1",
                        "priority": 300
                    }
                ]
            }
        }
    ]
}

スクリプトでCSV出力

template.jsonと↓のスクリプトを同じ階層に置いて実行すると、ルール種別ごとにファイアウォールポリシーを抽出してCSVで出力します。
私のPowerShell力が足りないこともあり色々加工しているのでちょっと無理矢理感があります。

Script(クリックして展開)
$jsonstr = Get-Content "$psscriptroot\template.json"
$jsonobj = $jsonstr | ConvertFrom-Json

$rulecoll_groups = ($jsonobj.resources | Where-Object  {$_.type -eq 'Microsoft.Network/firewallPolicies/ruleCollectionGroups'}).properties

#各ルールに親規則コレクショングループの優先度をマージ
foreach($rulecoll_group in $rulecoll_groups){
    $rulecoll_group | Foreach-Object {
        $_.ruleCollections.rules | Add-Member -MemberType NoteProperty -Name 'Priority(RuleCollectionGroup)' -Value $_.priority
        }
}
#各ルールに親規則コレクションの優先度と名前をマージ
foreach($rulecoll in $rulecoll_groups.rulecollections){
    $rulecoll | Foreach-Object {
        $_.rules | Add-Member -MemberType NoteProperty -Name 'Name(RuleCollection)' -Value $_.name
        $_.rules | Add-Member -MemberType NoteProperty -Name 'Priority(RuleCollection)' -Value $_.priority
        }
}

#エクスポート
$rules_dnat = $rulecoll_groups.ruleCollections.rules | Where-Object {$_.ruleType -eq 'NatRule'}
$rules_dnat | Select-Object `
                'Priority(RuleCollectionGroup)',`
                'Name(RuleCollection)',`
                'Priority(RuleCollection)',`
                name,`
                translatedAddress,`
                translatedPort,`
                @{Name="ipProtocols";Expression={$_.ipProtocols}},`
                @{Name="sourceAddresses";Expression={$_.sourceAddresses}},`
                @{Name="sourceIpGroups";Expression={$_.sourceIpGroups}},`
                @{Name="destinationAddresses";Expression={$_.destinationAddresses}},`
                @{Name="destinationPorts";Expression={$_.destinationPorts}}`
            | Export-Csv -NoTypeInformation $psscriptroot\natrule.csv

$rules_nw = $rulecoll_groups.ruleCollections.rules | Where-Object {$_.ruleType -eq 'NetworkRule'}
$rules_nw | Select-Object `
                'Priority(RuleCollectionGroup)',`
                'Name(RuleCollection)',`
                'Priority(RuleCollection)',`
                name,`
                @{Name="ipProtocols";Expression={$_.ipProtocols}},`
                @{Name="sourceAddresses";Expression={$_.sourceAddresses}},`
                @{Name="sourceIpGroups";Expression={$_.sourceIpGroups}},`
                @{Name="destinationAddresses";Expression={$_.destinationAddresses}},`
                @{Name="destinationIpGroups";Expression={$_.destinationIpGroups}},`
                @{Name="destinationFqdns";Expression={$_.destinationFqdns}},`
                @{Name="destinationPorts";Expression={$_.destinationPorts}}`
            | Export-Csv -NoTypeInformation $psscriptroot\nwrule.csv

$rules_app = $rulecoll_groups.ruleCollections.rules | Where-Object {$_.ruleType -eq 'ApplicationRule'}
$rules_app | Select-Object `
                'Priority(RuleCollectionGroup)',`
                'Name(RuleCollection)',`
                'Priority(RuleCollection)',`
                name,`
                @{Name="Protocols";Expression={$_.Protocols}},`
                @{Name="fqdnTags";Expression={$_.fqdnTags}},`
                @{Name="webCategories";Expression={$_.webCategories}},`
                @{Name="targetFqdns";Expression={$_.targetFqdns}},`
                @{Name="targetUrls";Expression={$_.targetUrls}},`
                terminateTLS,`
                @{Name="sourceAddresses";Expression={$_.sourceAddresses}},`
                @{Name="destinationAddresses";Expression={$_.destinationAddresses}},`
                @{Name="sourceIpGroups";Expression={$_.sourceIpGroups}}`
            | Export-Csv -NoTypeInformation $psscriptroot\apprule.csv

[注意点]

  • JSONの親子関係をCSVで表現するため、親のルールコレクショングループとルールコレクションに紐づく要素(優先度、ルールコレクション名)を各ルールにマージしています。
  • 配列になっているプロパティはそのままExport-Csvに渡すとSystem.Object[]と表示されてしまうので、Slect-Objectを使いScript Blockの中で文字列に変換する処理を入れます。[2]

表示例

  • DNATルール
  • ネットワークルール
  • アプリケーションルール

まとめ

ひとまずARMテンプレートのJSONからCSVを生成できるようになりました。
ただこの手の変換スクリプトを都度作るのも手間がかかるので、読むだけならJSON CrackJSON Viewerなどの視覚化ツールをローカルで用意してそのまま見るのが一番手っ取り早いですね。

あとはAzure Monitor+Azure Functionsでポリシー変更をトリガーにCSVを自動生成するようにワークフローを組むとか、template specs[3]を使ってポリシーのバージョン管理をしつつ差分を見るとか色々できそうなのでやる気が出たら試してみます。

脚注
  1. Firewall Policy を使用した規則の処理
    https://learn.microsoft.com/ja-jp/azure/firewall/rule-processing#rule-processing-using-firewall-policy ↩︎

  2. Select-ObjectでScript Blockを渡すと、Script Blockで評価された値がSelectできるようで覚えておくと便利そうです。そのままだとプロパティやメソッドがそのまま名前に反映されてしまいますが、@{Name="";Expression={Script Block}}のように渡すと別名を定義できます。
    https://learn.microsoft.com/ja-jp/powershell/module/microsoft.powershell.utility/select-object?view=powershell-7.3#11-inputobject ↩︎

  3. ARMテンプレートをバージョン管理できる機能です。
    https://learn.microsoft.com/ja-jp/azure/azure-resource-manager/templates/template-specs?tabs=azure-cli ↩︎

Discussion