Azure FirewallにOffice 365のルールを自動設定してみた

"Azure FirewallにOffice 365のルールを自動設定してみた" (2020年12月8日掲載)

目次

はじめに

初めまして、ソフトバンク株式会社 クラウドSE第1部の高田です。

今回Azure FirewallにOffice 365の通信を許可するルールの設定を自動で行えるかを試してみました。
現状(2020/11執筆時点)では通信を許可するルールの設定は手動で行う必要があり、Microsoft社が公開しているOffice 365のIPアドレス、URL一覧とそのバージョン情報が記載されたWebサイト(※参照) から許可すべき内容を確認して、それをAzure Firewallに手動でルールの設定をして、といった運用が必要になるかと思います。
そのプロセスを自動化できないか、今回はAzure Function上でPowerShellを動かすことで試してみました。

まずAzure Funcitonですが、先ほど少し触れましたが今回コードはPowershell、バージョンは6で作成しています。定期的に実行するようにTimerトリガーで作成しています。

今回のブログは主にスクリプトの内容の説明を中心に行っていきたいと思っています。
Azure Funcitonの使い方については、こちらのブログ を参考にしていただくと分かりやすいと思います。Azure Funcitonをお試しいただく際はご一読ください。

作成したスクリプトの概要

今回作成したスクリプトの動作の構成は、大きく以下のようになっています。

""

<スクリプトの構成>

入力

バージョン情報

""

<ローカル(ストレージアカウント)上のバージョン情報表示例>

このようにバージョン情報が書かれています。
ストレージアカウント上に配置しています。

URLのバージョン情報はこのような記載です。

<URL上のバージョン情報表示例>

latestの値と比較を行うために使用します。

処理

バージョン情報をもとに、URLから取得した最新バージョン情報の新旧を比較し、必要に応じて最新のエンドポイント一覧、および変更一覧の情報をもとにFirewallにルールを設定します。
詳細についてはこの後ご説明します。

出力

エンドポイント一覧

処理中にURLから取得したエンドポイント一覧をローカル(ストレージアカウント)に出力しているものです。

<ローカル(ストレージアカウント)上のエンドポイント一覧表示例>

変更一覧

処理中にURLから取得した変更一覧をローカル(ストレージアカウント)に出力しているものです。
ローカルのバージョン番号とURLの最新のバージョン番号の差分にあたる変更情報のみを取得するようにしています。

""

<ローカル(ストレージアカウント)上の変更一覧表示例>
上述の入力と出力は、AzureFunction上で、指定を行います。
「統合」というタブからそれぞれのソースを指定できます。
今回の関数の構成だと、以下のようになっています。

""

<統合の画面>

入力

(inputVersion) ⇒ バージョン情報

出力

(outputEndpoints) ⇒ エンドポイント一覧
(outputChanges) ⇒ 変更一覧
(outputVersion) ⇒ バージョン情報

バージョン情報については入力・出力とも同じファイルで、入力時にそれを読み込んで、出力時にそれに出力するといった流れになっています。
inputVersionなどの名前はコード内で変数として指定することができます。

各処理のご説明

ここから、今回作成したスクリプトの内容を順にご説明していきます。
作成したスクリプトの全量はこのブログの一番最後に載せています。
スクリプトを試していただく際は、くれぐれも自己責任にてお願いします。

1~13行目まではAzure Funcitonでタイマートリガーの関数を作成した後デフォルトで記載されている内容です。
2行目の引数を指定している内容には、先ほどの概要説明の中の入力の内容($inputVersionの箇所)を追記しています。
ここで指定した変数が関数起動時に引数として渡されます。



1.  # Input bindings are passed in via param block.
2.  param($Timer, $inputVersion)
3.  
4.  # Get the current universal time in the default string format
5.  $currentUTCtime = (Get-Date).ToUniversalTime()
6.  
7.  # The 'IsPastDue' porperty is 'true' when the current function invocation is later than scheduled.
8.  if ($Timer.IsPastDue) {
9.     Write-Host "PowerShell timer is running late!"
10. }
11. 
12. # Write an information log with the current time.
13. Write-Host "PowerShell timer trigger function ran! TIME: $currentUTCtime"



15行目の「# 個別処理ここから」からが今回のスクリプト個別処理の記載となってきます。

まずはスクリプトがAzure Firewallを操作できるようにするための記述です。
ログイン認証情報を指定(18~21行目)して、Azure FirewallがデプロイされたAzure環境にログイン(25行目)しています。
あらかじめAzureに登録しておいたアプリケーションの情報をもとにサービスプリンシパルを作成してAzure Firewallにアクセスできるようにします。(22行目) サービスプリンシパルについてピンとこない方は、以下のWebサイトが分かりやすく説明されています。ご参照ください。
Azure サービス プリンシパルの作成方法



15. # 個別処理ここから
16. 
17. # Azureログイン認証情報
18. $appid = "アプリケーションID"
19. $secret = "シークレットキー"
20. $tenantId = "テナントID"
21. $securedSecret = ConvertTo-SecureString $secret -AsPlainText -Force
22. $pscredential = New-Object System.Management.Automation.PSCredential($appid, $securedSecret)
23. 
24. # サービスプリンシパルを使用してAzureにログイン
25. Connect-AzAccount -ServicePrincipal -Tenant $tenantId -Credential $pscredential


28~30行目ではAzure Firewallの名前とリソースグループ名を指定してAzure Firewall情報を
変数に格納しています。
お試しいただく際は、それぞれの環境にあわせて指定してください。


27. # Firewall取得
28. $FwName = "ファイアウォール名"
29. $RGName = "リソースグループ名"
30. $azFw = Get-AzFirewall -Name $FwName -ResourceGroupName $RGName
31. 

33~39行目辺りで、スクリプトがアクセスするURL(バージョン情報、エンドポイント一覧)、ルールコレクション名を変数に格納しています。
33行目は、それぞれのURLにアクセスする際にランダムなリクエストIDを付けてリクエストする必要があり、そのIDを生成して変数に格納しています。


32. # 共通変数
33. $ClientRequestId = [GUID]::NewGuid().Guid
34. 
35. $VersiontPath = ("https://endpoints.office.com/version/Worldwide?ClientRequestId=" + $ClientRequestId)
36. $EndpointsPath = ("https://endpoints.office.com/endpoints/Worldwide?ClientRequestId="  + $ClientRequestId)
37. 
38. $AppRuleCollectionName = "o365_app"
39. $NetRuleCollectionName = "o365_nw"

42~56行目辺りでアプリケーション ルール コレクションおよびネットワーク ルール コレクションの初期作成ルールのパラメータを指定しています。
何故ここでそのような指定をしているかというと、それぞれのルールコレクションを新規で作成する際、最低でも1つのルールが作成されていなければエラーとなってしまうためです。そのため、ここでは新規でルールコレクションを作成する際に指定する一時的な(必要なルールを追加した後に削除する)ルールを作成するためのパラメータを指定しています。


41. # App初期作成ルール名、パラーメータ
42. $tmpAppRuleName = "tmp_app_rule"
43. $tmpAppSrc = "*"
44. $tmpAppProto = "https"
45. $tmpAppFQDN = "tmp.com"
46.   
47. # Net初期作成ルール名、パラーメータ
48. $tmpNetRuleName = "tmp_net_rule"
49. $tmpNetSrc = "127.0.0.1"
50. $tmpNetDst = "127.0.0.1"
51. $tmpNetProto = "UDP"
52. $tmpNetDport = "53"
53.   
54. # 初期作成ルール共通パラメータ
55. $ActionType = "Allow"
56. $Pri = 100
57. 

ここからは各関数のパートになります。

まずはルールコレクション関数です。
Office 365用のアプリケーション ルール コレクションおよびネットワーク ルール コレクションが対象のファイアウォールに存在するかしないかを判定して、コレクションがない場合はコレクションを作成します。
GetApplication(or Network)RuleCollectionByNameメソッドの返り値がnullでなければコレクションが存在すると判定、もしくはエラーが返ってきた場合はコレクションがないと判定し(66~73行目)、コレクションを作成します。スクリプト中のコメントにも記載していますが、
GetApplication(or Network)RuleCollectionByNameメソッドはコマンド名の通りコレクション名からコレクションを取得するメソッド(※参照)であり、対象のコレクション名の存在有無を返すメソッドではないため、スクリプトの記述のようにエラーで存在有無を判定するある意味苦肉の策をとっています。(71~73行目)
75~77行目でアプリケーション ルール コレクション、92~94行目でネットワーク ルール コレクションを作成しています。作成時には42~56行目辺りのご説明をしました、一時ルールを作成してそれをコレクションに含まれるルールとして、コレクションを作成しています。


61. # ルールコレクション作成関数
62. function CreateRuleCollection {
63.   
64.    #Appルールコレクションの有り無しを判定、無しの場合Appコレクションを作成
65.    try {
66.        $AppRuleCollection = $azFw.GetApplicationRuleCollectionByName($AppRuleCollectionName)
67.        if ($AppRuleCollection -ne $null) {
68.            Write-Information "AppRuleCollection already exist"
69.        }
70.        # コレクションの存在を確認できるコマンドがない為エラーをキャッチしその内容で判定する。
71.    } catch [ArgumentException]{
72.        $e = $_.Exception
73.        if ($e -Like "*Collection with name o365_app does not exist*") {
74.            Write-Information "AppRuleCollection not exist then create"
75.            $tmpAppRule = New-AzFirewallApplicationRule -Name $tmpAppRuleName -SourceAddress $tmpAppSrc -Protocol $tmpAppProto -TargetFqdn $tmpAppFQDN
76.            $AppRuleCollection = New-AzFirewallApplicationRuleCollection -Name $AppRuleCollectionName -Priority $Pri -Rule $tmpAppRule -ActionType $ActionType
77.            $Azfw.AddApplicationRuleCollection($AppRuleCollection)
78.        }
79.    }
80. 
81.    #Netルールコレクションの有り無しを判定、無しの場合Netコレクションを作成
82.    try {
83.        $NetRuleCollection = $azFw.GetNetworkRuleCollectionByName($NetRuleCollectionName)
84.        if ($NetRuleCollection -ne $null) {
85.            Write-Information "NetRuleCollection already exist"
86.        }
87.        # コレクションの存在を確認できるコマンドがない為エラーをキャッチしその内容で判定する。
88.    } catch [ArgumentException]{
89.        $e = $_.Exception
90.        if ($e -Like "*Collection with name o365_nw does not exist*") {
91.            Write-Information "NetRuleCollection not exist then create"
92.            $tmpNetRule = New-AzFirewallNetworkRule -Name $tmpNetRuleName -Protocol $tmpNetProto -SourceAddress $tmpNetSrc -DestinationAddress $tmpNetDst -DestinationPort $tmpNetDport
93.            $NetRuleCollection = New-AzFirewallNetworkRuleCollection -Name $NetRuleCollectionName -Priority $Pri -Rule $tmpNetRule -ActionType "Allow"
94.            $Azfw.AddNetworkRuleCollection($NetRuleCollection)
95.        }
96.  }
97.}
98. 

続いての関数はアプリケーション・ネットワーク各ルール作成のための共通処理関数です。
まず少し厄介だったのはURLのエンドポイント一覧上、許可するポートはポート番号で書かれています(80,443...etc)

<URLのエンドポイントの例>

ですがルール作成の際に指定する場合はポート指定でなくプロトコル指定(http,https)となるため、ポート⇒プロトコルへの変換が必要でした。

""

<Azure Firewallのルール設定画面の例>

その処理を110~131行目で行っています。
ルール名については、エンドポイント一覧内の各エンドポイントにはidが付与されていますので、それぞれのidの値に対応するルール名(“id_idの値”)を付けてルールを作成する処理になっています。(103行目)

<URLのエンドポイントidの例>

142~166行目辺りで、実際に各ルールを定義して作成を行っています。


98. 
99. # ルール作成・変更共通処理
100.    function DefineRule($Endpoint) {
101.    
102.       # ルール名生成
103.       $RuleName = "id_" + $Endpoint.id
104.       # プロトコル生成
105.       # tcpPortsの値によって条件分岐、80及び443はそれぞれhttp,httpsに変換する(そうしない場合エラーとなる為)
106.       # tcpでなくuppPortsがある場合がありその場合はtcpPortsのnull判定をしてupdPortsと判断する。
107.       # http,https以外のプロトコルはFWの仕様上ネットワークコレクションでルール定義しなくてはいけないため
108.       # 80,443以外はネットワークコレクションルールを作成するようにする
109.       switch ($Endpoint.tcpPorts) {
110.           "80,443" {
111.               $Proto = @("http","https")
112.               $RuleCollection = $AppRuleCollection
113.           }
114.           "443" {
115.               $Proto = "https"
116.               $RuleCollection = $AppRuleCollection
117.           }
118.           "80" {
119.               $Proto = "http"
120.               $RuleCollection = $AppRuleCollection
121.           }
122.           $null {
123.               $Proto = $Endpoint.udpPorts.Split(",")
124.               $RuleCollection = $NetRuleCollection
125.               $NetProto ="UDP"
126.           }
127.           default {
128.               $Proto = $Endpoint.tcpPorts.Split(",")
129.               $RuleCollection = $NetRuleCollection
130.               $NetProto ="TCP"
131.           }
132.       }
133.       # ターゲットFQDN生成
134.       $FQDN = $Endpoint.urls
135.       # ソースアドレスを指定
136.       $SrcAddr = "*"
137.       # デスティネーションアドレス生成
138.       $DstAddr = $Endpoint.ips
139.    
140.       # ターゲットのコレクションに応じてルールを作成追加
141.       if ($RuleCollection -eq $AppRuleCollection) {
142.           $AppRule = New-AzFirewallApplicationRule -Name $RuleName -SourceAddress $SrcAddr -Protocol $Proto -TargetFqdn $FQDN
143.           try {
144.               $RuleCollection.AddRule($AppRule)
145.           } catch {
146.               $e = $_.Exception
147.               if ($e -Like "*$RuleName name is already used*") {
148.                   Write-Information "Rule with name $RuleName already exist then skip adding"
149.               } else {
150.                   Write-Information "error occured while adding apprule for some reasons"
151.               }
152.           }
153.       } elseif ($RuleCollection -eq $NetRuleCollection) {
154.           $NetRule = New-AzFirewallNetworkRule -Name $RuleName -Protocol $NetProto -SourceAddress $SrcAddr -DestinationAddress $DstAddr -DestinationPort $Proto
155.           try {
156.               $RuleCollection.AddRule($NetRule)
157.           } catch {
158.               $e = $_.Exception
159.               if ($e -Like "*$RuleName name is already used*") {
160.                   Write-Information "Rule with name $RuleName already exist then skip adding"
161.               } else {
162.                   Write-Information "error occured while adding netrule for some reasons"
163.               }
164.           }
165.     }
166. }
167.    

次の関数は全てのルールを作成する関数です。
スクリプトの実行が初回であると判断された場合に呼び出されます。
共通処理関数を、エンドポイント一覧から取得した各エンドポイントごと(各idごと)に引数として渡して呼び出し、各ルールを作成します。(176~185行目)
その後初期作成ルールを削除しています。(188~200行目)


168.    # ルール全量作成関数
169.    function CreateAllRules($Endpoints) {
170.    
171.       # ルールコレクション取得
172.       $AppRuleCollection = $azFw.GetApplicationRuleCollectionByName($AppRuleCollectionName)
173.       $NetRuleCollection = $azFw.GetNetworkRuleCollectionByName($NetRuleCollectionName)
174.      
175.       # 最新版エンドポイントからルールを作成
176.       $Endpoints | ForEach-Object {
177.           try {
178.               $Endpoint = $_
179.               # ルール作成・変更共通処理呼び出し
180.               DefineRule $Endpoint
181.           } catch {
182.               $e = $_.Exception
183.               Write-Information $e
184.           }
185.       }
186.    
187.       # 初期作成ルール削除
188.       try {
189.           $AppRuleCollection.RemoveRuleByName($tmpAppRuleName)
190.       } catch {
191.           $e = $_.Exception
192.           Write-Information $e
193.       }
194.    
195.       try {
196.           $NetRuleCollection.RemoveRuleByName($tmpNetRuleName)
197.       } catch {
198.           $e = $_.Exception
199.           Write-Information $e
200.     }
201.
202. }
203.    

続いてルールを変更する関数です。
この関数は変更一覧を引数にしてメイン処理から呼び出されます。
現時点(2020/11執筆時点)でルールの内容を変更するというコマンド、メソッドがAzureで用意されていないため、変更情報上のエンドポイントidに対応する作成済みのルールをいったん削除、その後変更履歴の内容にあわせて再作成する(追加分は新規作成を行う)という処理を行っています。(224~261行目)

<URL上の変更一覧の例>


204.    
205.    
206.    # ルール変更関数
207.    function ChangeRules ($Chaneges, $Endpoints){
208.    
209.       # ルールコレクション取得
210.       $AppRuleCollection = $azFw.GetApplicationRuleCollectionByName($AppRuleCollectionName)
211.       $NetRuleCollection = $azFw.GetNetworkRuleCollectionByName($NetRuleCollectionName)
212.    
213.       $Chaneges | ForEach-Object {
214.           try {
215.               $Change = $_
216.               $search_id = $Change.endpointSetId
217.               $Endpoints | ForEach-Object {
218.                   $Endpoint = $_
219.    
220.                   # 変更情報と最新情報を比較し、変更情報に一致したidを削除、再作成する
221.                   # 削除、再作成としているのは、ルール内容を変更するメソッドが存在しないため
222.                   # switchの分岐は、ルール名を名前からアプリケーションコレクションルールかネットワークコレクションルールか
223.                   # 判定できないため、プロトコルの種類によって判定している。
224.                   if ($search_id -eq $Endpoint.id) {
225.                       switch ($Endpoint.tcpPorts) {
226.                           "80,443" {
227.                           $RemoveRuleCollection = $AppRuleCollection
228.                           }
229.                           "443" {
230.                           $RemoveRuleCollection = $AppRuleCollection
231.                           }
232.                           $null {
233.                           $RemoveRuleCollection = $NetRuleCollection
234.                           }
235.                           default {
236.                           $RemoveRuleCollection = $NetRuleCollection
237.                           }
238.                       }
239.                       $remove_rulename = "id_" + $search_id
240.                       try {
241.                           $RemoveRuleCollection.RemoveRuleByName($remove_rulename)
242.                       } catch {
243.                           $e = $_.Exception
244.                           if ($e -Like "*Rule with name $remove_rulename does not exist*") {
245.                               Write-Information "Rule with name $remove_rulename does not exist then skip removing"
246.                           } else {
247.                               Write-Information "error occured while removing rule for some reasons"
248.                           }
249.                       }
250.                       # ルール定義関数呼び出し、変更ファイルのdispositionがRemoveだった場合、呼び出さない。(削除後、再作成しない。)
251.                       if ($Change.disposition -in ("Add", "Change")) {
252.                           DefineRule $Endpoint
253.                       }
254.                   }
255.               }
256.           } catch {
257.               $e = $_.Exception
258.               Write-Information $e
259.           }
260.     }
261.    }
262.    

各関数はここまでとなり、ここからはメイン処理となります。
スクリプトが起動すると、まずはバージョンファイルをチェックして、ファイルがない場合は初期値(0000000000)のファイルを作成します。(270~272行目)
バージョンファイルが存在する場合、その内容をローカルのバージョンとして設定します。(274~276行目)
次にURLのバージョン情報を取得し(281~282行目)、ローカルのバージョンと新旧比較を行います。(292行目)
ローカルのバージョンが古かった場合、各関数を呼び出し、ルール設定変更を行います。
ルール設定変更を行う場合まず、エンドポイント一覧をURLから取得します。
ローカルのバージョンが初期値だった場合、初回起動とみなし、ルール全量を作成する関数を呼び出します。(306~317行目)
ローカルのバージョンが初期値以降だった場合、変更情報をURLから取得して、変更のあった分のみの設定変更を行います(323~336行目)
最後に設定変更を確定します。(339行目)


263.    
264.    
265.    # メイン処理
266.    
267.    Write-Information "fw-rule-autoconfig function start"
268.    
269.    # ローカルのバージョンファイルの存在をチェックし、ない場合は初期値をセットする
270.    if ($inputVersion -eq $null) {
271.       Write-Information "local version file not exist then set default value 0000000000"
272.       $LocalVersion = "0000000000"
273.    # バージョンファイルが存在する場合、Functionのインプットをセットする
274.    } else {
275.       Write-Information "local version file exist then skip set default value"
276.       $LocalVersion = $inputVersion
277.    }
278.    
279.    # URLから最新バージョン取得
280.    Write-Information "get newest version from URL"
281.       $URLVersion = Invoke-RestMethod -Uri $VersiontPath
282.       $URLVersion_Latest = $URLVersion.latest
283.    Write-Information "get newest version from URL done"
284.    
285.    # 最新バージョン出力
286.    Write-Information "Write out version to local start"
287.       # Push-OutputBinding -Name outputVersion -Value "2020042800"
288.       Push-OutputBinding -Name outputVersion -Value $URLVersion_Latest
289.    Write-Information "Write out version to local done"
290.    
291.    # URLのバージョンとローカルのバージョンを比較し、URLのバージョンが新しかった場合、各処理を行う
292.    if ($URLVersion_Latest -gt $LocalVersion) {
293.       Write-Information "Local version is $LocalVersion URL version is $URLVersion_Latest"
294.    
295.       #最新版のendpointsを取得する
296.       Write-Information "get latest endpoints from URL"
297.           $Endpoints = Invoke-RestMethod -Uri $EndpointsPath
298.       Write-Information "get latest endpoints from URL done"
299.      
300.       # エンドポイントファイル出力
301.       Write-Information "Write out endpoints to local"
302.           Push-OutputBinding -Name outputEndpoints -Value $Endpoints
303.       Write-Information "Write out endpoints to local done"
304.    
305.       #ローカルバージョンがデフォルトだった場合、初回起動時とみなし、ルールコレクション作成、最新版全量のルール作成を行う
306.       if ($LocalVersion -eq "0000000000") {
307.          
308.           Write-Information "local version is default value then create rulecollections and all rules"
309.           # ルールコレクション作成
310.           Write-Information "create rulecollections"
311.               CreateRuleCollection
312.           Write-Information "create rulecollections done"
313.          
314.           # ルール作成
315.           Write-Information "create rules"
316.               CreateAllRules $Endpoints
317.           Write-Information "create rules done"
318.    
319.       } else {       
320.           # ローカルのバージョン以降の変更ファイルを取得する
321.          
322.           Write-Information "get change file later than version $LocalVersion from URL"
323.               $Changepath = ("https://endpoints.office.com/changes/worldwide/" + $LocalVersion + "?clientrequestid=" + $ClientRequestId)
324.               $Chaneges = Invoke-RestMethod $Changepath
325.    
326.           Write-Information "get change file later than version $LocalVersion from URL done"
327.           # 変更がある場合、ローカルに出力し、FWに変更を反映させる
328.           if ($Chaneges -ne $null) {
329.               #変更ファイルローカル出力
330.               Write-Information "Write out changes to local"
331.                   Push-OutputBinding -Name outputChanges -Value $Chaneges
332.               Write-Information "Write out changes to local done"
333.              
334.               #変更ファイル設定変更
335.               Write-Information "reflect changes to firewall"
336.                   ChangeRules $Chaneges $Endpoints
337.               Write-Information "reflect changes to firewall done"
338.           }
339.       }
340.    
341.       #変更をfwに反映
342.       Write-Information "set changes to firewall"
343.           Set-AzFirewall -AzureFirewall $azfw -AsJob       
344.       Write-Information "set changes to firewall done"
345.    
346.    } else {
347.       Write-Information "local version is newest then nothing was done"
348.    }
349.    
350.    Write-Information "fw-rule-autoconfig function done"
351.    

そしてこのスクリプトを実行すると、Azure Firewallに許可が必要なルールが設定、反映されます、、となるはずだったのですが、上記までのコードで実行するとルール作成の箇所でエラーになってしまうと思います。

""

<実行時エラー画面>

エンドポイントid154の「autodiscover. * .onmicrosoft.com」というURLは無効なURLだと言われています。
このURLは公開されているエンドポイント一覧から取得しているので、それを無効と言われても困るのですが、ドキュメント を読むと「現在、ワイルドカードは FQDN の左側でのみ使用できます。 例えば、 * .contoso.com や * contoso.com などです。」
となっています。エラーの原因となっている対象のURLは文字列中に「 * 」が存在するため指定できないURLとなり、現状はそのまま指定できないためスキップするしかなさそうです。
ちなみにエンドポイントid78で「 * .onmicrosoft.com」を許可するというルールが指定されているので、そこで包含的に許可するはずなので、以下の箇所を書き換えました。


177.           try {
178.               $Endpoint = $_
179.               if ($Endpoint.id -ne "154") {
180.                   # ルール作成・変更共通処理呼び出し
181.                   DefineRule $Endpoint
182.       }
183.    

苦肉の策といえます。

書き換えた上で実行すると、

<実行成功ログ画面>

実行が成功して、Azure PortalのFirewall画面からも

""

<Azure Firewall ルール画面 ネットワークルールコレクション>

""

<Azure Firewall ネットワークルールコレクション設定画面>

""

<Azure Firewall ルール画面 アプリケーションルールコレクション画面>

<Azure Firewall ルール画面 アプリケーションルール設定画面>

それぞれのコレクション、各ルールが設定されていることが確認できます。

さいごに

最後に今回作成したスクリプトの全量をお載せしておきます。
実際に運用環境で使用していくにはエラー処理や、条件分岐の組み立て方、コマンドの使い方などブラッシュアップが必要な部分が必ず出てくるかと思います。
今回は作成したスクリプトの内容を部分部分でご説明していきましたが、それらが私と同じようにAzure Firewallのルールをスクリプトで自動設定できないだろうか、とお考えの方がスクリプトの作成を検討される際の一材料になれば幸いと思っています。
なお、ソフトバンクではAzure、Office365だけではなく
「Windows Virtual Desktop」や「ゼロトラストセキュリティ」含めたご支援も可能です。ぜひ、ご相談ください。

最後までお読み頂きありがとうございました。

<スクリプト全量>


1.  # Input bindings are passed in via param block.
2.  param($Timer, $inputVersion)
3.  
4.  # Get the current universal time in the default string format
5.  $currentUTCtime = (Get-Date).ToUniversalTime()
6.  
7.  # The 'IsPastDue' porperty is 'true' when the current function invocation is later than scheduled.
8.  if ($Timer.IsPastDue) {
9.     Write-Host "PowerShell timer is running late!"
10. }
11. 
12. # Write an information log with the current time.
13. Write-Host "PowerShell timer trigger function ran! TIME: $currentUTCtime"
14. 
15. # 個別処理ここから
16. 
17. # Azureログイン認証情報
18. $appid = "アプリケーションID"
19. $secret = "シークレットキー"
20. $tenantId = "テナントID"
21. $securedSecret = ConvertTo-SecureString $secret -AsPlainText -Force
22. $pscredential = New-Object System.Management.Automation.PSCredential($appid, $securedSecret)
23. 
24. # サービスプリンシパルを使用してAzureにログイン
25. Connect-AzAccount -ServicePrincipal -Tenant $tenantId -Credential $pscredential
26. 
27. # Firewall取得
28. $FwName = "ファイアウォール名"
29. $RGName = "リソースグループ名"
30. $azFw = Get-AzFirewall -Name $FwName -ResourceGroupName $RGName
31. 
32. # 共通変数
33. $ClientRequestId = [GUID]::NewGuid().Guid
34. 
35. $VersiontPath = ("https://endpoints.office.com/version/Worldwide?ClientRequestId=" + $ClientRequestId)
36. $EndpointsPath = ("https://endpoints.office.com/endpoints/Worldwide?ClientRequestId="  + $ClientRequestId)
37. 
38. $AppRuleCollectionName = "o365_app"
39. $NetRuleCollectionName = "o365_nw"
40. 
41. # App初期作成ルール名、パラーメータ
42. $tmpAppRuleName = "tmp_app_rule"
43. $tmpAppSrc = "*"
44. $tmpAppProto = "https"
45. $tmpAppFQDN = "tmp.com"
46.   
47. # Net初期作成ルール名、パラーメータ
48. $tmpNetRuleName = "tmp_net_rule"
49. $tmpNetSrc = "127.0.0.1"
50. $tmpNetDst = "127.0.0.1"
51. $tmpNetProto = "UDP"
52. $tmpNetDport = "53"
53.   
54. # 初期作成ルール共通パラメータ
55. $ActionType = "Allow"
56. $Pri = 100
57. 
58. 
59. ####################################### 各関数ここから ###############################################
60. 
61. # ルールコレクション作成関数
62. function CreateRuleCollection {
63.   
64.    #Appルールコレクションの有り無しを判定、無しの場合Appコレクションを作成
65.    try {
66.        $AppRuleCollection = $azFw.GetApplicationRuleCollectionByName($AppRuleCollectionName)
67.        if ($AppRuleCollection -ne $null) {
68.            Write-Information "AppRuleCollection already exist"
69.        }
70.        # コレクションの存在を確認できるコマンドがない為エラーをキャッチしその内容で判定する。
71.    } catch [ArgumentException]{
72.        $e = $_.Exception
73.        if ($e -Like "*Collection with name o365_app does not exist*") {
74.            Write-Information "AppRuleCollection not exist then create"
75.            $tmpAppRule = New-AzFirewallApplicationRule -Name $tmpAppRuleName -SourceAddress $tmpAppSrc -Protocol $tmpAppProto -TargetFqdn $tmpAppFQDN
76.            $AppRuleCollection = New-AzFirewallApplicationRuleCollection -Name $AppRuleCollectionName -Priority $Pri -Rule $tmpAppRule -ActionType $ActionType
77.            $Azfw.AddApplicationRuleCollection($AppRuleCollection)
78.        }
79.    }
80. 
81.    #Netルールコレクションの有り無しを判定、無しの場合Netコレクションを作成
82.    try {
83.        $NetRuleCollection = $azFw.GetNetworkRuleCollectionByName($NetRuleCollectionName)
84.        if ($NetRuleCollection -ne $null) {
85.            Write-Information "NetRuleCollection already exist"
86.        }
87.        # コレクションの存在を確認できるコマンドがない為エラーをキャッチしその内容で判定する。
88.    } catch [ArgumentException]{
89.        $e = $_.Exception
90.        if ($e -Like "*Collection with name o365_nw does not exist*") {
91.            Write-Information "NetRuleCollection not exist then create"
92.            $tmpNetRule = New-AzFirewallNetworkRule -Name $tmpNetRuleName -Protocol $tmpNetProto -SourceAddress $tmpNetSrc -DestinationAddress $tmpNetDst -DestinationPort $tmpNetDport
93.            $NetRuleCollection = New-AzFirewallNetworkRuleCollection -Name $NetRuleCollectionName -Priority $Pri -Rule $tmpNetRule -ActionType "Allow"
94.            $Azfw.AddNetworkRuleCollection($NetRuleCollection)
95.        }
96.    }
97. }
98. 
99. # ルール作成・変更共通処理
100.    function DefineRule($Endpoint) {
101.    
102.       # ルール名生成
103.       $RuleName = "id_" + $Endpoint.id
104.       # プロトコル生成
105.       # tcpPortsの値によって条件分岐、80及び443はそれぞれhttp,httpsに変換する(そうしない場合エラーとなる為)
106.       # tcpでなくuppPortsがある場合がありその場合はtcpPortsのnull判定をしてupdPortsと判断する。
107.       # http,https以外のプロトコルはFWの仕様上ネットワークコレクションでルール定義しなくてはいけないため
108.       # 80,443以外はネットワークコレクションルールを作成するようにする
109.       switch ($Endpoint.tcpPorts) {
110.           "80,443" {
111.               $Proto = @("http","https")
112.               $RuleCollection = $AppRuleCollection
113.           }
114.           "443" {
115.               $Proto = "https"
116.               $RuleCollection = $AppRuleCollection
117.           }
118.           "80" {
119.               $Proto = "http"
120.               $RuleCollection = $AppRuleCollection
121.           }
122.           $null {
123.               $Proto = $Endpoint.udpPorts.Split(",")
124.               $RuleCollection = $NetRuleCollection
125.               $NetProto ="UDP"
126.           }
127.           default {
128.               $Proto = $Endpoint.tcpPorts.Split(",")
129.               $RuleCollection = $NetRuleCollection
130.               $NetProto ="TCP"
131.           }
132.       }
133.       # ターゲットFQDN生成
134.       $FQDN = $Endpoint.urls
135.       # ソースアドレスを指定
136.       $SrcAddr = "*"
137.       # デスティネーションアドレス生成
138.       $DstAddr = $Endpoint.ips
139.    
140.       # ターゲットのコレクションに応じてルールを作成追加
141.       if ($RuleCollection -eq $AppRuleCollection) {
142.           $AppRule = New-AzFirewallApplicationRule -Name $RuleName -SourceAddress $SrcAddr -Protocol $Proto -TargetFqdn $FQDN
143.           try {
144.               $RuleCollection.AddRule($AppRule)
145.           } catch {
146.               $e = $_.Exception
147.               if ($e -Like "*$RuleName name is already used*") {
148.                   Write-Information "Rule with name $RuleName already exist then skip adding"
149.               } else {
150.                   Write-Information "error occured while adding apprule for some reasons"
151.               }
152.           }
153.       } elseif ($RuleCollection -eq $NetRuleCollection) {
154.           $NetRule = New-AzFirewallNetworkRule -Name $RuleName -Protocol $NetProto -SourceAddress $SrcAddr -DestinationAddress $DstAddr -DestinationPort $Proto
155.           try {
156.               $RuleCollection.AddRule($NetRule)
157.           } catch {
158.               $e = $_.Exception
159.               if ($e -Like "*$RuleName name is already used*") {
160.                   Write-Information "Rule with name $RuleName already exist then skip adding"
161.               } else {
162.                   Write-Information "error occured while adding netrule for some reasons"
163.               }
164.           }
165.       }
166.    }
167.    
168.    # ルール全量作成関数
169.    function CreateAllRules($Endpoints) {
170.    
171.       # ルールコレクション取得
172.       $AppRuleCollection = $azFw.GetApplicationRuleCollectionByName($AppRuleCollectionName)
173.       $NetRuleCollection = $azFw.GetNetworkRuleCollectionByName($NetRuleCollectionName)
174.      
175.       # 最新版エンドポイントからルールを作成
176.       $Endpoints | ForEach-Object {
177.           try {
178.               $Endpoint = $_
179.               if ($Endpoint.id -ne "154") {
180.                   # ルール作成・変更共通処理呼び出し
181.                   DefineRule $Endpoint
182.               }
183.           } catch {
184.               $e = $_.Exception
185.               Write-Information $e
186.           }
187.       }
188.    
189.       # 初期作成ルール削除
190.       try {
191.           $AppRuleCollection.RemoveRuleByName($tmpAppRuleName)
192.       } catch {
193.           $e = $_.Exception
194.           Write-Information $e
195.       }
196.    
197.       try {
198.           $NetRuleCollection.RemoveRuleByName($tmpNetRuleName)
199.       } catch {
200.           $e = $_.Exception
201.           Write-Information $e
202.       }
203.    
204.    }
205.    
206.    # ルール変更関数
207.    function ChangeRules ($Chaneges, $Endpoints){
208.    
209.       # ルールコレクション取得
210.       $AppRuleCollection = $azFw.GetApplicationRuleCollectionByName($AppRuleCollectionName)
211.       $NetRuleCollection = $azFw.GetNetworkRuleCollectionByName($NetRuleCollectionName)
212.    
213.       $Chaneges | ForEach-Object {
214.           try {
215.               $Change = $_
216.               $search_id = $Change.endpointSetId
217.               $Endpoints | ForEach-Object {
218.                   $Endpoint = $_
219.    
220.                   # 変更情報と最新情報を比較し、変更情報に一致したidを削除、再作成する
221.                   # 削除、再作成としているのは、ルール内容を変更するコマンドが存在しないため
222.                   # switchの分岐は、ルール名を名前からアプリケーションコレクションルールかネットワークコレクションルールか
223.                   # 判定できないため、プロトコルの種類によって判定している。
224.                   if ($search_id -eq $Endpoint.id) {
225.                       switch ($Endpoint.tcpPorts) {
226.                           "80,443" {
227.                           $RemoveRuleCollection = $AppRuleCollection
228.                           }
229.                           "443" {
230.                           $RemoveRuleCollection = $AppRuleCollection
231.                           }
232.                           $null {
233.                           $RemoveRuleCollection = $NetRuleCollection
234.                           }
235.                           default {
236.                           $RemoveRuleCollection = $NetRuleCollection
237.                           }
238.                       }
239.                       $remove_rulename = "id_" + $search_id
240.                       try {
241.                           $RemoveRuleCollection.RemoveRuleByName($remove_rulename)
242.                       } catch {
243.                           $e = $_.Exception
244.                           if ($e -Like "*Rule with name $remove_rulename does not exist*") {
245.                               Write-Information "Rule with name $remove_rulename does not exist then skip removing"
246.                           } else {
247.                               Write-Information "error occured while removing rule for some reasons"
248.                           }
249.                       }
250.                       # ルール定義関数呼び出し、変更ファイルのdispositionがRemoveだった場合、呼び出さない。(削除後、再作成しない。)
251.                       if ($Change.disposition -in ("Add", "Change")) {
252.                           DefineRule $Endpoint
253.                       }
254.                   }
255.               }
256.           } catch {
257.               $e = $_.Exception
258.               Write-Information $e
259.           }
260.       }
261.    }
262.    
263.    ####################################### 各関数ここまで ###############################################
264.    
265.    # メイン処理
266.    
267.    Write-Information "fw-rule-autoconfig function start"
268.    
269.    # ローカルのバージョンファイルの存在をチェックし、ない場合は初期値をセットする
270.    if ($inputVersion -eq $null) {
271.       Write-Information "local version file not exist then set default value 0000000000"
272.       $LocalVersion = "0000000000"
273.    # バージョンファイルが存在する場合、Functionのインプットをセットする
274.    } else {
275.       Write-Information "local version file exist then skip set default value"
276.       $LocalVersion = $inputVersion
277.    }
278.    
279.    # URLから最新バージョン取得
280.    Write-Information "get newest version from URL"
281.       $URLVersion = Invoke-RestMethod -Uri $VersiontPath
282.       $URLVersion_Latest = $URLVersion.latest
283.    Write-Information "get newest version from URL done"
284.    
285.    # 最新バージョン出力
286.    Write-Information "Write out version to local start"
287.       # Push-OutputBinding -Name outputVersion -Value "2020042800"
288.       Push-OutputBinding -Name outputVersion -Value $URLVersion_Latest
289.    Write-Information "Write out version to local done"
290.    
291.    # URLのバージョンとローカルのバージョンを比較し、URLのバージョンが新しかった場合、各処理を行う
292.    if ($URLVersion_Latest -gt $LocalVersion) {
293.       Write-Information "Local version is $LocalVersion URL version is $URLVersion_Latest"
294.    
295.       #最新版のendpointsを取得する
296.       Write-Information "get latest endpoints from URL"
297.           $Endpoints = Invoke-RestMethod -Uri $EndpointsPath
298.       Write-Information "get latest endpoints from URL done"
299.      
300.       # エンドポイントファイル出力
301.       Write-Information "Write out endpoints to local"
302.           Push-OutputBinding -Name outputEndpoints -Value $Endpoints
303.       Write-Information "Write out endpoints to local done"
304.    
305.       #ローカルバージョンがデフォルトだった場合、初回起動時とみなし、ルールコレクション作成、最新版全量のルール作成を行う
306.       if ($LocalVersion -eq "0000000000") {
307.          
308.           Write-Information "local version is default value then create rulecollections and all rules"
309.           # ルールコレクション作成
310.           Write-Information "create rulecollections"
311.               CreateRuleCollection
312.           Write-Information "create rulecollections done"
313.          
314.           # ルール作成
315.           Write-Information "create rules"
316.               CreateAllRules $Endpoints
317.           Write-Information "create rules done"
318.    
319.       } else {       
320.           # ローカルのバージョン以降の変更ファイルを取得する
321.          
322.           Write-Information "get change file later than version $LocalVersion from URL"
323.               $Changepath = ("https://endpoints.office.com/changes/worldwide/" + $LocalVersion + "?clientrequestid=" + $ClientRequestId)
324.               $Chaneges = Invoke-RestMethod $Changepath
325.    
326.           Write-Information "get change file later than version $LocalVersion from URL done"
327.           # 変更がある場合、ローカルに出力し、FWに変更を反映させる
328.           if ($Chaneges -ne $null) {
329.               #変更ファイルローカル出力
330.               Write-Information "Write out changes to local"
331.                   Push-OutputBinding -Name outputChanges -Value $Chaneges
332.               Write-Information "Write out changes to local done"
333.              
334.               #変更ファイル設定変更
335.               Write-Information "reflect changes to firewall"
336.                   ChangeRules $Chaneges $Endpoints
337.               Write-Information "reflect changes to firewall done"
338.           }
339.       }
340.    
341.       #変更をfwに反映
342.       Write-Information "set changes to firewall"
343.           Set-AzFirewall -AzureFirewall $azfw -AsJob
344.       Write-Information "set changes to firewall done"
345.    
346.    } else {
347.       Write-Information "local version is newest then nothing was done"
348.    }
349.    
350.    Write-Information "fw-rule-autoconfig function done"
351.
352.
353.    

関連サービス

Microsoft Azureは、Microsoftが提供するパブリッククラウドプラットフォームです。コンピューティングからデータ保存、アプリケーションなどのリソースを、必要な時に必要な量だけ従量課金で利用することができます。→詳細はこちら