Azure Monitor アラートを仮想マシン内の PowerShell スクリプトに通知する


# はじめに

Azure Monitor のアラートが発報されたら、どのようにして仮想マシン内の PowerShell スクリプトを実行 (アラートの内容も引き渡す) できるか検証してみました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/230264/b4343db8-0912-ce64-53cf-ff96d7aabe67.png)


# アラートのアクションとアラートのスキーマ

Azure Monitor のアラートからプライベートな環境にある PowerShell スクリプトを直接実行できません。
よって、通知されたアラートを仲介する「何か」が必要になるでしょう。Azure Monitor のアクショングループでは、アクションの構成でアラートをトリガーとするアクションを定義することができます。今回はアラートの仲介者として、Automation Runbook と Logic Apps を使ってみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/230264/0cf086af-8666-5fed-dff8-f9c3480e7b0c.png)


https://docs.microsoft.com/ja-jp/azure/azure-monitor/alerts/action-groups?WT.mc_id=AZ-MVP-5003113

また、発報されるアラート種類は、以下の 3 種類あります。

- メトリック
- ログ
- アクティビティ ログ

これらのアラートのスキーマは、それぞれのアラート コンテキストが取り扱いやすい「共通アラート スキーマ」を使用します。詳しくは、以下のドキュメントをご覧ください。

https://docs.microsoft.com/ja-jp/azure/azure-monitor/alerts/alerts-common-schema?WT.mc_id=AZ-MVP-5003113

https://docs.microsoft.com/ja-jp/azure/azure-monitor/alerts/alerts-common-schema-definitions?WT.mc_id=AZ-MVP-5003113


# アラートを受け取る

アラートが Automation Runbook や Logic Apps ロジックアプリを呼び出す時は、HTTP POST 要求で行われ、POST 要求の本文 (Body) には、JSON 形式でアラートに関する情報が含まれています。


## Azure Automation Runbook の場合

https://docs.microsoft.com/ja-jp/azure/automation/automation-create-alert-triggered-runbook?WT.mc_id=AZ-MVP-5003113

上記のドキュメントを参考に Runbook を作成します。```WebhookData``` 入力パラメーターでアラートを受け取れます。Runbook の大枠は以下のようなかんじになりますね。

```powershell:Runbook
param
(
    [Parameter (Mandatory=$false)][object] $WebhookData
)
if ($WebhookData)
{
    ### ここに処理を実装 ###
    ### アラートの内容は $WebhookData.RequestBody に格納されている
}
else
{
    Write-Error "This runbook is meant to be started from an Azure alert webhook only."
}
```


## Azure Logic Apps ロジックアプリの場合

https://docs.microsoft.com/ja-jp/azure/azure-monitor/alerts/alerts-common-schema-integrations?WT.mc_id=AZ-MVP-5003113

上記のドキュメントを参考にロジックアプリを作成します。「When a HTTP request is received (HTTP 要求の受信時) トリガー (Method=POST)」を選択して、「Request Body JSON Schema」パラメーターに上記のドキュメントに示されているスキーマをコピー＆ペーストします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/230264/7cceb7bf-12c7-6571-a52b-fc1f70a90cde.png)


## アクション グループのアクションを構成する

- Azure Portal を使用したアクショングループの作成

https://docs.microsoft.com/ja-jp/azure/azure-monitor/alerts/action-groups?WT.mc_id=AZ-MVP-5003113#create-an-action-group-by-using-the-azure-portal

上記のドキュメントの「Azure Portal を使用したアクショングループの作成」を参考にアクショングループを作成します。[アクション] タブでアクションを構成できますので、ここに先ほど作成した Automation Runbook または Logic Apps ロジックアプリを指定します。その際、「共通アラート スキーマ」を有効にしておきましょう。

- Azure Automation Runbook の場合 

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/230264/206ad452-f4b6-ff7c-4db9-8e50e468ed65.png)

- Azure Logic Apps ロジックアプリの場合

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/230264/aff040a0-d9dd-2ea3-c9ea-b88bce7be013.png)


## アクション グループをテストする

Azure ポータル上から作成したアクション グループに対して疎通テストを行うことができます。
アクション (Automation Runbook または Logic Apps ロジックアプリ) に届くアラート コンテキストはさまざまなアラート (内容はサンプルです) を選択できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/230264/c1d24146-df47-7ad8-93e8-85b9efe579b8.png)

- Azure portal でアクション グループをテストする

https://docs.microsoft.com/ja-jp/azure/azure-monitor/alerts/action-groups?WT.mc_id=AZ-MVP-5003113#test-an-action-group-in-the-azure-portal-preview


# 仮想マシンの外から PowerShell スクリプトを実行する

Automation Runbook および Logic Apps ロジックアプリから、どのようにして仮想マシン内の PowerShell スクリプトを実行するか。仮想マシンにコマンドをリモートで実行させるための手段 (いろいろある) として、Azure では REST API でも行えます。

https://docs.microsoft.com/ja-jp/rest/api/compute/virtual-machines/run-command?WT.mc_id=AZ-MVP-5003113&tabs=HTTP


## 仮想マシンの実行コマンドのアクセス許可

REST API 経由で仮想マシンでコマンドを実行するためには、アクセス元に対して仮想マシンへのアクセス許可を割り当てる必要があります。
よって、まずはアクセス元である Automation アカウントまたは Logic Apps ロジックアプリでマネージド ID を有効にして、次にアクセス先の仮想マシンでアクセス元のマネージド ID にロールを割り当ててアクセスを許可します。今回は組み込みロールの「[仮想マシン共同作成者](https://docs.microsoft.com/ja-jp/azure/role-based-access-control/built-in-roles?WT.mc_id=AZ-MVP-5003113#virtual-machine-contributor)」を割り当てます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/230264/f9fee2b7-ac81-fe40-6904-9419d93774ff.png)

- Azure Automation の場合

https://docs.microsoft.com/ja-jp/azure/automation/learn/powershell-runbook-managed-identity?WT.mc_id=AZ-MVP-5003113

- Azure Logic Apps ロジックアプリの場合

https://docs.microsoft.com/ja-jp/azure/logic-apps/create-managed-service-identity?WT.mc_id=AZ-MVP-5003113&tabs=consumption


## REST API 経由で仮想マシンのコマンドを実行する

https://docs.microsoft.com/ja-jp/rest/api/compute/virtual-machines/run-command?WT.mc_id=AZ-MVP-5003113&tabs=HTTP

仮想マシンでのコマンドをリモートで実行させるための URI は以下のとおり。

```
POST https://management.azure.com/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Compute/virtualMachines/{vmName}/runCommand?api-version=2021-04-01
```

- URI パラメーター

URI 内にあるパラメーターを環境に合わせて以下のように置換します。

| URI パラメーター | 設定値 |
|:-|:-|
| {subscriptionId} | サブスクリプション ID |
| {resourceGroupName} | リソース グループ名 |
| {vmName} | 仮想マシン名 |

- リクエストヘッダー

リクエストヘッダーには、マネージド ID を基に取得したアクセストークンを含めます。

| キー | 値 |
|:-|:-|
| Content-Type | application/json |
| Authorization | Bearer {アクセストークン} |

- リクエスト本文

リクエスト本文は、JSON 形式で以下のように記述します。

```json:Body
{
    "commandId": "RunPowerShellScript",
    "parameters": [
        { "name": "パラメーター名1", "value": "パラメーター値1" },
        { "name": "パラメーター名2", "value": "パラメーター値2" }
    ],
    "script": [ "実行するスクリプト" ]
}
```

| name | 型 | 説明 |
|:-|:-|:-|
| commandId | string | コマンドを識別する ID。必須。PowerShell スクリプトの場合は「RunPowerShellScript」。 |
| parameters | key-value の配列 | コマンドのパラメーター。※今回は使わなかった |
| script | string の配列 | 実行するスクリプト。 |


### Azure Automation Runbook の場合

Runbook の実装は以下のようになります。

```powershell:Runbook
[OutputType("PSAzureOperationResponse")]
param
(
	[Parameter (Mandatory=$false)][object] $WebhookData
)

# Runbook で AzContext を継承しない
Disable-AzContextAutosave -Scope Process | Out-Null

# システム割り当てマネージド ID を使用して Azure に接続
try
{
	$azContext = (Connect-AzAccount -Identity).context
}
catch
{
	Write-Output "There is no system-assigned user identity. Aborting."; 
	exit
}

# コンテキストの設定と保存
$azContext = Set-AzContext -SubscriptionName $azContext.Subscription -DefaultProfile $azContext

if ($WebhookData)
{
	# アクセストークンの取得
	$azProfile = [Microsoft.Azure.Commands.Common.Authentication.Abstractions.AzureRmProfileProvider]::Instance.Profile
	$profileClient = New-Object -TypeName Microsoft.Azure.Commands.ResourceManager.Common.RMProfileClient -ArgumentList ($azProfile)
	$token = $profileClient.AcquireAccessToken($azContext.Subscription.TenantId)

	# ヘッダーの定義
	$authHeader = @{
		'Content-Type'='application/json'
		'Authorization'="Bearer $($token.AccessToken)"
	}

	# REST API の URI の定義
	$targetResourceGroupName = 'rg-demomonitor'
	$targetVmName = 'vm-demo222'
	$restUri = "https://management.azure.com/subscriptions/$($azContext.Subscription.Id)/resourceGroups/$targetResourceGroupName/providers/Microsoft.Compute/virtualMachines/$targetVmName/runCommand?api-version=2020-06-01"

	# ボディの定義
	## アラート内のシングルクォーテーションを置換
	## ※VM 内のスクリプトには文字列として引き渡すので、シングルクォーテーションが含まれていると途中で途切れてしまう
	$strParam = $WebhookData.RequestBody.Replace("'","^^")
	$body = @{
		'commandId' = 'RunPowerShellScript'
		'script' = @(
			"C:\temp\test.ps1 -Alert '$strParam'"
		)
	}

	$response = Invoke-webrequest -Uri $restUri -Method Post -Headers $authHeader -Body $($body | ConvertTo-Json) -UseBasicParsing
	Write-Output "response: " + $response
}
else
{
	Write-Error "This runbook is meant to be started from an Azure alert webhook only."
}
```

仮想マシンでのコマンドをリモートで実行させるためにリクエストする際のリクエストヘッダーに含めるアクセストークンは、マネージド ID を基に取得します。

```powershell
# アクセストークンの取得
$azProfile = [Microsoft.Azure.Commands.Common.Authentication.Abstractions.AzureRmProfileProvider]::Instance.Profile
$profileClient = New-Object -TypeName Microsoft.Azure.Commands.ResourceManager.Common.RMProfileClient -ArgumentList ($azProfile)
$token = $profileClient.AcquireAccessToken($azContext.Subscription.TenantId)

# ヘッダーの定義
$authHeader = @{
	'Content-Type'='application/json'
	'Authorization'="Bearer $($token.AccessToken)"
}
```

URI は、前述したように URI 内にあるパラメーターを環境に合わせて編集します。

```powershell
# REST API の URI の定義
$targetResourceGroupName = 'rg-demomonitor'
$targetVmName = 'vm-demo222'
$restUri = "https://management.azure.com/subscriptions/$($azContext.Subscription.Id)/resourceGroups/$targetResourceGroupName/providers/Microsoft.Compute/virtualMachines/$targetVmName/runCommand?api-version=2020-06-01"
```

もしアラートを仮想マシン内の PowerShell スクリプトへ引数 (文字列) として引き渡す場合は、アラート内にはシングルクォーテーションが含まれている場合があるので他の文字 (含まれていなさそうな文字) に置換したほうが良いでしょう。

```powershell
# ボディの定義
## アラート内のシングルクォーテーションを置換
## ※VM 内のスクリプトには文字列として引き渡すので、シングルクォーテーションが含まれていると途中で途切れてしまう
$strParam = $WebhookData.RequestBody.Replace("'","^^")
$body = @{
	'commandId' = 'RunPowerShellScript'
	'script' = @(
		"C:\temp\test.ps1 -Alert '$strParam'"
	)
}
```

仮想マシンでのコマンドをリモートで実行させるためにリクエストするため、```Invoke-webrequest``` コマンドを使います。

```powershell
$response = Invoke-webrequest -Uri $restUri -Method Post -Headers $authHeader -Body $($body | ConvertTo-Json) -UseBasicParsing
```


### Azure Logic Apps アプリの場合

Azure Automation Runbook と処理内容はほぼ同じで、以下のようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/230264/a4af36ae-6ba8-17f0-59f6-c8038f1c6152.png)

(1) REST API の URI の定義

URI は、前述したように URI 内にあるパラメーターを環境に合わせて編集します。

```
https://management.azure.com/subscriptions/@{variables('SubscriptionId')}/resourceGroups/@{variables('targetResourceGroupName')}/providers/Microsoft.Compute/virtualMachines/@{variables('targetVmName')}/runCommand
```

(2) ボディの定義 (アラートの加工)

もしアラートを仮想マシン内の PowerShell スクリプトへ引数 (文字列) として引き渡す場合は、アラート内にはシングルクォーテーションが含まれている場合があるので他の文字 (含まれていなさそうな文字) に置換したほうが良いでしょう。

```
replace(string(triggerBody()),outputs('Single_quotation'),outputs('Substitute_chara'))
```

(3) HTTP リクエスト

仮想マシンでのコマンドをリモートで実行させるためにリクエストするアクションを以下のようにします。

| パラメーター | 設定値 |
|:-|:-|
| Method | POST |
| URI | (1) で編集した REST API の URI |
| Headers | key : ```Content-Type```<br />value : ```application/json``` |
| Queries | key : ```api-version```<br />value : ```2020-06-01``` |
| Body | { ```"commandId": "RunPowerShellScript", "script": [ "C:\\temp\\test.ps1 -Alert '{(2) で編集したアラート}'" ] }``` |
| Cookie | (空白) |
| Authentication<br /> > Authentication type | マネージド ID |
| Authentication<br /> > Managed identity | システム割り当てマネージド ID |
| Authentication<br /> > Audience | (空白) |


# まとめ

Azure Monitor のアクショングループのアクション、マネージド ID、仮想マシンの実行コマンドを組み合わせて、Azure Monitor アラートを仮想マシン内の PowerShell スクリプトに通知する方法をこのようにやってみました。Azure の提供機能だけでここまで実現できるなんてとても便利ですね。素晴らしいですね。:heart_eyes:

※この投稿内容は、あくまで検証結果から見出した方法です。
※ご参考される際は、十分な検証・テストを行い、実環境上で問題なく利用できるか見極めたうえで、ご判断いただければと思います。:bow:


### <font color="red">追記 (2022/07/20)</font>

以下のドキュメントに記載されているとおりいろいろと「制限」がありますので、ご一読ください。

https://docs.microsoft.com/ja-jp/azure/virtual-machines/windows/run-command?WT.mc_id=AZ-MVP-5003113#restrictions