Azure Datacenter IP Rangesが変更されたらSlackに通知する

AzureのPaaSやパブリックIPに割り当てられるグローバルアドレスのIPレンジは不定期で更新されます。
最新のIPレンジは以下のサイトからxml形式でダウンロードすることが可能です。

[Microsoft Azure Datacenter IP Ranges](https://www.microsoft.com/en-us/download/details.aspx?id=41653)

例えばこのIPレンジを使って、NSG設定とか、社内LAN環境のExpressRouteルーティング設定をしているとかの場合、いちいちダウンロードして更新チェックするのはとても手間です。
東日本リージョンもしくは西日本リージョンのIPレンジが変更された場合にSlack通知させる仕組みを作りました。

# 前提

## 概要イメージ図

![image.png](https://qiita-image-store.s3.amazonaws.com/0/180335/40ae3325-ecf1-4ba3-8e16-c30ae805de5e.png)

FunctionsはIP Rangeが書かれたXMLファイルをダウンロードして、指定されたリージョンだけに加工します。
AutomationはFunctionsから取得したIP RangeをBlobに加工して、前日分と差分がないか確認します。
差分があれば、変更点をSlackに通知します。

## ローカルPC上でFunctionsの実行環境が整っていること
まとめた記事を書いたので参考にしてください。事前インストールの章ですね。
[Azure FunctionsをJava + VS Codeで試してみた（Maven for Java編）](https://qiita.com/shingo_kawahara/items/435941731cade567218f#%E4%BA%8B%E5%89%8D%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB)

## 課金確認専用ユーザ作成
以下記事を参考に課金用サービスプリンシパルを作成します。
[Azureの課金系REST APIをcurlで実行する方法](https://qiita.com/shingo_kawahara/items/d7e9c4e395624259c05a#%E8%AA%B2%E9%87%91%E7%94%A8%E3%82%B5%E3%83%BC%E3%83%93%E3%82%B9%E3%83%97%E3%83%AA%E3%83%B3%E3%82%B7%E3%83%91%E3%83%AB%E4%BD%9C%E6%88%90)

# Azure Functions実装

それでは早速Functionsから実装していきます。
コードは以下に配置しています。

[Azure-IpRange-Api-Sample](https://github.com/shingo-kawahara/Azure-IpRange-Api-Sample)

## git clone

* git cloneでリポジトリを持ってくる。

```
git clone https://github.com/shingo-kawahara/Azure-IpRange-Api-Sample.git
```

## pom.xml修正

* pom.xmlを開いて、2箇所修正します。
    * functionAppName： これはFunction Appの名前になります。
    * functionResourceGroup： これはFunction Appが配置されるリソースグループ名です。

```java
	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<maven.compiler.source>1.8</maven.compiler.source>
		<maven.compiler.target>1.8</maven.compiler.target>
		<azure.functions.maven.plugin.version>1.0.0-beta-7</azure.functions.maven.plugin.version>
		<azure.functions.java.library.version>1.0.0-beta-5</azure.functions.java.library.version>
		<functionAppName>azure-iprange-api-sample</functionAppName>
		<functionAppRegion>japaneast</functionAppRegion>
		<stagingDirectory>${project.build.directory}/azure-functions/${functionAppName}</stagingDirectory>
		<functionResourceGroup>azure-iprange-api-sample-grp</functionResourceGroup>
	</properties>
```

## local.settings.json配置

* 新規ファイル作成する。
* ローカルPCでテストするとき、もしネットワーク上にProxyサーバがいる場合は、このファイルにIDとパスワードを設定します。

```json
{
  "IsEncrypted": false,
  "Values": {
    "AzureWebJobsStorage": "",
    "AzureWebJobsDashboard": "",
    "FUNCTIONS_WORKER_RUNTIME": "java",
    "proxyHost": "Your Proxy HostName",
    "proxyPort": "Your Proxy Port",
    "proxyUser": "Your Proxy UserName",
    "ProxyPass": "Your Proxy Password"
  }
}
```

## ローカルテスト

* 以下コマンドを実行して、ビルドとローカル実行をします。

```
mvn clean package
mvn azure-functions:run
```

* Webブラウザを開いて、以下のURLを入力します。

```
http://localhost:7071/api/iprange?region=japaneast
```

* 指定したリージョンのIPアドレス一覧が表示されたらOKです。

## デプロイ

* 以下コマンドを実行して、Azure上にデプロイします。

```
az login
mvn azure-functions:deploy
```

## Functions Key取得
* デプロイしたFunctionsを選択し、[管理]を選択します。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/180335/dc4b6435-c97d-5070-a1d5-8d97a556ada3.png)

* [関数キー]をコピーしておきます。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/180335/f6432eac-4740-88a6-4a47-345ad2f09006.png)

## テスト

* Webブラウザを開いて、以下のURLを入力します。ローカル実行と比べて、Functions Keyを追加で付与しています。

```
https://azure-iprange-api-sample.azurewebsites.net/api/iprange?code=[Functions Key]&region=japanwest
```

* 指定したリージョンのIPアドレス一覧が表示されたらOKです。

# Blob Storage作成

* IPレンジ情報を格納するBlob Storageを作成しておきます。コンテナーまで作っておいてください。

# Azure Automation実装

Functions APIの実行は、Automationにさせることにします。
ここはなんでもよくて、オンプレからcronとかで定期実行してもよいです。
Automationアカウントの作成手順は割愛します。

* Runbookの種類は「PowerShell」で、以下のように作成します。
* 更新チェックをするために、ストレージアカウントに事前事後ファイルを配置することにしています。

```
Param(
    [parameter(Mandatory=$True)]
    [string]$functionsKey,

    [parameter(Mandatory=$True)]
    [string]$resourceGroup,

    [parameter(Mandatory=$True)]
    [string]$storageAccountName,

    [parameter(Mandatory=$True)]
    [String]$ContainerName
)

$Conn = Get-AutomationConnection -Name AzureRunAsConnection
Add-AzureRMAccount -ServicePrincipal -Tenant $Conn.TenantID -ApplicationID $Conn.ApplicationID -CertificateThumbprint $Conn.CertificateThumbprint

Set-AzureRmCurrentStorageAccount -ResourceGroupName $resourceGroup -StorageAccountName $storageAccountName

[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
foreach( $region in "japaneast","japanwest" )
{
    Write-Output "Start Get Blob List $region"
    $ErrorActionPreference = "silentlycontinue"
    $BlobList = Get-AzureStorageBlob -Container $ContainerName -Blob $region-bef.txt
    $ErrorActionPreference = "continue"

    Write-Output "Start Get Iprange $region"
    $IpRange = Invoke-WebRequest -Uri "https://azure-iprange-api-sample/api/iprange" -Method GET -Body @{code="$functionsKey";region="$region"} -UseBasicParsing | select -ExpandProperty Content
    if ( [string]::IsNullOrEmpty($BlobList) ) {
        Write-Output "Start Create Before File $region"
        $IpRange | Out-File C:\$region-bef.txt
        Set-AzureStorageBlobContent -File C:\$region-bef.txt -Container $ContainerName
    } else {
        Write-Output "Start Create After File $region"
        $IpRange | Out-File C:\$region-aft.txt
        Get-AzureStorageBlobContent -Container $ContainerName -Blob $region-bef.txt -Destination "C:\" -Force
        $bef = Get-Content C:\$region-bef.txt
        $aft = Get-Content C:\$region-aft.txt
        Compare-Object $bef $aft > C:\$region-cmp.txt
        $msg = Get-Content C:\$region-cmp.txt

        if ( -not([string]::IsNullOrEmpty($msg)) ) {
            $output = $msg -join "`n"
            $pretext = "$region" + " リージョンのIPRangeが更新されました"
            $enc = [System.Text.Encoding]::GetEncoding('ISO-8859-1')
            $utf8Bytes = [System.Text.Encoding]::UTF8.GetBytes($pretext)
            $payload = @{
                username = "Azure Automation"
                icon_emoji = ":azure:"
                channel = "#channel"
                attachments = @(
                    @{
                        pretext = $enc.GetString($utf8Bytes);
                        text = $output
                    }
                )
            }
            Write-Output "Start Slack Notify $region"
            Invoke-RestMethod -Uri "https://hooks.slack.com/services/[Slack Incoming Webhook ID]" -Method Post -Body (ConvertTo-Json $payload)
            Set-AzureStorageBlobContent -File C:\$region-aft.txt -Container $ContainerName -Blob $region-bef.txt -Force
        }
    }
}
```

* スケジュール設定をして、IPRangeに変更があった場合にSlack通知されればOKです。  
![image.png](https://qiita-image-store.s3.amazonaws.com/0/180335/14fff0f0-a3c2-2756-69e5-9b78883b9585.png)

* Slackじゃなくてメール通知させたい場合は、Automation側で制御すればOKです。

# まとめ

今回は通知だけですが、ARMTemplateやAnsibleといったIaCの仕組みと一緒に活用するのがよさそう。