前回のブログでCODEBLUE とBlack Hat Europe Arsenalで疑似攻撃ツール、Power Automate C2(以下、PAC2)を発表したことを紹介しました。今回はその実装にあたって調査したPower Automateのクラウドフローの仕様について紹介します。また、次回の記事では、PAC2の実装のために作成したクラウドフローの生成を支援するPythonライブラリを紹介します。PAC2の概要やコンセプトについては、以前の記事を参照ください。
Power Automateとは
Power AutomateはMicrosoft社が提供するビジネスフローの自動化に特化したサービスです。ユーザーはPower Automate上でさまざまなアプリケーションやサービス間とデータの処理を行うワークフローを定義し、実行することで作業を自動化できます。ユーザーがワークフローを作成する操作はすべてWeb GUIで完結するため、ソースコードを記述せずにアプリケーションを開発できるノーコード・ローコードツールとも呼ばれています。また、Micorosoft 365やAzureといったMicrosoft社のサービスに限らず、GoogleやDropboxといったさまざまなサードパーティのサービスと連携する機能を提供しています。Power Automateは幅広いサービスと連携できるため、さまざまなビジネスシーンで利用できるサービスです。
Power Automate におけるフロー
Power Automateで作業の自動化を行うフローには3種類あります。
- クラウドフロー
- デスクトップフロー
- ビジネスフロー
クラウドフローはMicrosoftが提供するサービス基盤上でフローが実行されるため、ユーザーはフローを実行する環境を準備することなく、即座にPower Automateのサービス上で実行できます。PAC2ではクラウドフローを利用してC2機能を実現しています。本記事ではPAC2で利用しているクラウドフローのみに焦点をあてます。他のフローの詳細については、Microsoft社の公式ドキュメントを参照ください。
クラウドフローの作成方法
クラウドフローを作成する方法は3つあります。
- Power AutomateのポータルページのWeb UI上で作成する
- Power Automateのポータルページからをソリューションまたはパッケージをインポートする
- Power Automate Management Connectorの"Create Flow" Actionを利用して作成する
1はWeb UI上でユーザーが操作を行い作成する方法です。こちらの方法は公式ドキュメントやさまざまな記事で紹介されているため本記事では割愛します。2は、事前に1で作成したクラウドフローをエクスポートしたソリューションまたはパッケージをインポートする方法です。パッケージを用いたインポート/エクポートはzip形式で圧縮されたJSONのファイル群を用いられます。パッケージの詳細については記事の後半で紹介します。3はPower Automate Management Connectorに含まれる"Create Flow" Actionを利用して作成する方法です。この方法を用いるとクラウドフローから別のクラウドフローを作成できます。この方法でクラウドフローを作成するには、クラウドフローを定義したJSONデータを用意する必要があります。
しかしながら、2で使用されるパッケージの仕様や3の"Create Flow" Actionで用いるクラウドフローを定義したJSONデータのフォーマットについて解説したドキュメントはほとんどありません。今回の記事は、この部分を補完することを目的としています。なお、PAC2では2のパッケージのインポートと3の"Create Flow" Actionを利用した方法を使用してクラウドフローの作成しています。
クラウドフローの構成要素
ここからはクラウドフローの全体像を解説します。クラウドフローを構成する要素は大きく分けて以下の4つになります。
- Connector
- Trigger
- Action
- Connection
それぞれの要素は図で示すような内包関係になっています。一般的なプログラミング言語で例えると、ライブラリはConnectorに該当し、ライブラリが提供する関数はTriggerやActionに該当すると考えられます。
ここではそれぞれの要素について解説します。
Connector
ConnectorはPower Automateでさまざまな外部サービスを利用するために実装されたコンポーネントの集まりです。Connectorを通じて、Microsoft 365やAzure、Googleといったさまざまなサービスとデータ交換します。それぞれのConnectorは後述するTriggerまたはActionを提供します。たとえばOutlookのConnectorは「メール一覧を取得する」「メールを送信する」といった機能をActionとして提供します。2024年1月現在、1112種類のConnectorが提供されています。
Trigger
Triggerとはクラウドフローを開始するための条件を設定するイベントです。一定のスケジュールに沿って実行するイベントやOffice 365 Outlookで新しいメールを受信したときに実行するイベントなど、さまざまなサービスと連携し、開始条件を設定できます。2024年1月現在、241種類のTriggerが提供されています。
Action
ActionはConnectorが提供する機能を指します。Power Automateでは、変数の設定から、データベースの読み書き、HTTP通信、Outlookのメール取得など、さまざまな操作をするActionが提供されています。クラウドフローでは提供されるActionを組み合わせ、ユーザーが実現したいワークフローを組み上げていきます。
Connection
ConnectionはConnectorが各サービスを利用するための認証情報などを含む接続情報を保持します。ユーザーがTeamsなどの外部サービスへのアクセスするTriggerやActionを利用するには、TeamsへアクセスするConnectorと接続情報を入力したConnectionを用意する必要があります。
クラウドフローとConnector
クラウドフローの作成はConnectorに定義されたActionをつなぎ合わせて、実行したいワークフローを作る作業になります。また同時に、それぞれのConnectorを利用するために必要な接続情報(認証情報)を入力します。これらの作業はPower Automateのポータル上で完結します。
図は作成されたクラウドフローとConnector、外部サービスの関係を示したものになります。
クラウドフローの内部仕様
ここまでクラウドフローはConnnectorに定義されているTriggerとActionを組み合わせて実現していることを説明しました。
ここからは、クラウドフローの定義はPower Automate上でどのように表現されているか見ていきましょう。ユーザーが定義したクラウドフローはすべてJSON形式で保持されます。ここでは、そのJSON形式がどのようになっているか解説します。
クラウドフローのJSONコードは以下に示すようなネスト構造で構成されてます。
{
"definition": {
"triggers":{
"trigger_1" : {...},
},
"actions": {
"action_1" : {...},
"action_2" : {...},
...
}
},
"connectionReference": [
"connection_A": {...},
...
],
}
ここからは、それぞれの要素、trigger(s)
、 action(s)
、 connectionReference
について解説します。
definition
definition内はクラウドフローの動作を定義している部分で、TriggersとActionsの2つの部分に別れています。
triggers/trigger
triggers
はクラウドフローの開始条件を設定する部分になります。triggers
には原則1つの trigger
が含まれます。
以下のコードは手動トリガーのイベントを行うJSONコードです。手動トリガーのようにPower Automateにデフォルトで組み込まれている trigger
は type
,kind
,inputs
を設定する仕様になっています。
"trigger_name": {
"type": "Request",
"kind": "Button",
"inputs": {
"schema": {
"type": "object",
"properties": {},
"required": []
}
}
}
以下の例は、Outlook v3 Connectorで定義されたTriggerになります。新規メールを受信した場合に、CloudFlowが実行されます。こちらの場合は type
,inputs
を設定します。inputs
内の host
パラメーターにConnectorを設定し、parameters
にTriggerの動作条件を設定します。
"trigger_name":{
"type": "OpenApiConnectionNotification",
"inputs": {
"parameters": {
"includeAttachments": false,
"importance": "Any",
"fetchOnlyWithAttachment": false,
"folderPath": "Inbox"
},
"host": {
"apiId": "/providers/Microsoft.PowerApps/apis/shared_office365",
"connection": "shared_office365",
"operationId": "OnNewEmailV3"
}
},
}
actions/action
actions
はクラウドフローで実行する各種操作をまとめたものになり、1つないし、複数の action
で構成されます。action
はそれぞれのconnectorで定義された操作になります。ここでは基本的actionであるconnectorが提供するactionとPower Automate組み込みの action
をそれぞれ見てみましょう。
まずはじめに、Teams connectorが提供するTeamsのチャンネルのメッセージを取得する action
です。action
では、type
,inputs
,runAfter
を設定します。Connectorが提供するactionでは、inputs
内の host
パラメーターにConnectorを設定し、parameters
にactionの動作を設定します。runAfter
ではその action
が actions
内での実行順とその条件指定されます。runAfter
が空白である場合は actions
内の先頭で実行される action
であることを示します。
"action_1": {
"type": "OpenApiConnection",
"inputs": {
"parameters": {
"groupId": "bd4ae720-9036-4f26-b400-36be91ea169b",
"channelId": "19:b30eaed914b4475da4c933df66ecd643@thread.tacv2"
},
"host": {
"apiId": "/providers/Microsoft.PowerApps/apis/shared_teams",
"connection": "shared_teams",
"operationId": "GetMessagesFromChannel"
}
},
"runAfter": {
}
}
次に、組み込みactionである、変数の初期化を見てみます。組み込みactionは type
と input
,runAfter
でactionが定義されています。input
に定義される要素には、各組み込み action
それぞれに用意された要素を使用します。変数に関わるactionでは、variables
が inputs
内に設定されます。runAfter
では同様にactionの動作順と条件が定義されています。以下の例では、action_1
が成功したあとに action_2
が実行されます。
"action_2": {
"type": "InitializeVariable",
"inputs": {
"variables": [
{
"name": "var1",
"type": "integer",
"value": 0
}
]
},
"runAfter": {
"action_1": ["Succeeded"]
}
}
Action間のデータ参照
actionを定義していく際に、前に実行したactionの結果を参照したい場面は多々でてきます。その際はワークフロー式関数を利用します。この関数はJSON内の値として利用できることができ、クラウドフローを実行する際に動的に展開されます。たとえば、"action_1"として定義されたactionの実行結果にアクセスする場合は、"@body('action_1')"
のように表記しアクセスできます。このワークフロー式関数は実行時には以下のような形式のJSONに展開され、実行結果が格納されます。
"body":{
# Actionごとの実行結果
}
actionの実行結果のアクセスの他にも数値計算やさまざまなワークフロー式関数が用意されています。
詳細は以下の公式ドキュメントを参照ください。
Reference guide to workflow expression functions in Azure Logic Apps and Power Automate
Flow制御に関わるaction
組み込みのactionに条件分岐や繰り返しなど、フローの制御に関わるactionが用意されています。ここではいくつか条件分岐と繰り返しの制御を見てみます。
If action
条件分岐の action
は、条件を定義する expression
と条件が真のときに実行される actions
、偽のときに実行される else
内の actions
があります。条件の定義は独自のJSON形式の構文が用いられ、JSONのネストが深い部分から評価される仕様になっているようです。また、真偽それぞれに設定される actions
は definition
で使用される actions
と同様の形式を使用します。
"if_action": {
"type": "If",
"expression": {
"and": [
{"equals": ["@variables('var1')",1]},
{"equals": ["@variables('var1')",2]}
]
},
"actions": {
...
},
"else": {
"actions": {
...
}
},
"runAfter": {
"action_1": ["Succeeded"]
}
}
Foreach action
for文のactionでは foreach
に含まれる配列の各要素に対して、定義された actions
を実行します。通常は配列の要素を1つずつ直列に実行しますが、runtimeConfiguration
を設定することで、配列の各要素に対して並列実行を行うこともできます。
"for_loop": {
"type": "Foreach",
"foreach": "@variables('int_array')",
"actions": {
...
},
"runAfter": {
"Init_Int_Array": [
"Succeeded"
]
},
"runtimeConfiguration": {
"concurrency": {
"repetitions": 20
}
}
}
この他にも Until
,Switch
,Scope
といったフロー制御のactionが用意されています。
connectionReference
connectionReferenceでは利用するConnectorのIDと認証情報を含む接続情報のオブジェクトのマッピングを管理する配列を持ちます。以下のコードはOutlook connectionで利用する接続情報(認証情報)を含むオブジェクトとTeams connectionで利用する接続情報(認証情報)を含むオブジェクトを指定しています。
"connectionReferences": [
{
"id": "/providers/Microsoft.PowerApps/apis/shared_office365users",
"connectionName": "1419633963d917ba7fa7294fe739d393"
},
{
"id": "/providers/Microsoft.PowerApps/apis/shared_teams",
"connectionName": "51a04e1d0686c8d56aaa48e2c1aac4e4"
}
]
ここで設定する id
と connectionName
はPower AutomateのWeb GUIのURLのクエリから参照できます。Power AutomateのWeb UIから Connections > Connection Name
を選択すると、URLクエリに表示されます。
クラウドフローを定義したJSONデータの活用
ここまで、クラウドフローがどのようにJSONで定義されているか解説しました。ここからは、クラウドフローを定義したJSONファイルをもちいてクラウドフローを作成する方法を見てみましょう。
クラウドフローのパッケージ
パッケージはクラウドフローのインポート・エクポートに使われるzipファイルです。パッケージは決められたディレクトリ構造を持つJSONのファイル郡をzip形式で圧縮して作られます。ディレクトリ構造は以下のとおりです。
./
├── manifest.json
└── Microsoft.Flow
└── flows
├── fd42eda9-5fd3-4b66-b4b4-8e45254a27ca
│ ├── apisMap.json
│ ├── connectionsMap.json
│ └── definition.json
└── manifest.json
それでは、それぞれのファイルの内容と構成について解説します。
manifest.json
このファイルでは、パッケージのメタ情報とパッケージが使用するConnectorやConnection、クラウドフローといったリソースとそれぞれの依存関係が定義されます。リソースにはそれぞれUUID4の文字列がIDとして付与されます。それぞれのIDがUUID4のフォーマットを満たし、かつ、同一パッケージ内で重複しなければどのような値を用いても問題ありません。以下の図は manifest.json
で定義されるパッケージ内のリソースの依存関係を示したものです。
Microsoft.Flow/flows/manifest.json
このファイルではクラウドフローが定義されているUUID4を明示します。
{
"packageSchemaVersion": "1.0",
"flowAssets": {
"assetPaths": ["fd42eda9-5fd3-4b66-b4b4-8e45254a27ca"]
}
}
Microsoft.Flow/flows/[UUID4]/definition.json
このファイルはクラウドフローの定義とメタ情報を保持します。データは以下のようなフォーマットになります。
{
"name": "72a5ab79-c93a-41db-83df-c038386228aa",
"id": "/providers/Microsoft.Flow/flows/72a5ab79-c93a-41db-83df-c038386228aa",
"type": "Microsoft.Flow/flows",
"properties": {
"apiId": "/providers/Microsoft.PowerApps/apis/shared_logicflows",
"displayName": "Cloud Flow Name",
"definition": {
... # クラウドフローの定義
},
"connectionReferences": {
... # クラウドフローが使うConnectorとConnectionのマッピング
},
"flowFailureAlertSubscribed": false,
"isManaged": false
}
}
Microsoft.Flow/flows/[UUID4]/apisMap.json
このファイルはクラウドフローで使用するConnectorの一覧を保持します。データは以下のようなフォーマットになります。
{
"shared_flowmanagement": "fb6631d7-f8c0-4753-9df9-66e8260d7c3b",
"shared_dropbox": "d29d4358-bd10-475d-8fcc-0f676badc83e"
}
Microsoft.Flow/flows/[UUID4]/connectionsMap.json
このファイルはクラウドフローで使用するConnectionの一覧を保持します。データは以下のようなフォーマットになります。
{
"shared_flowmanagement": "0921af8e-4c83-41e7-b70f-28fa62f0ad06",
"shared_dropbox": "34be9512-dcf2-4050-8bde-b00327e776aa"
}
ここまで解説したフォーマットを満たすzipファイルを作成することで、Power Automateのポータルページからパッケージをインポートできるようになります。
Power Automate Management Connectorの"Create Flow" Action
ここではPower Automate Management Connectorの"Create Flow" Actionを用いたクラウドフロー作成方法を解説します。
"Create Flow" Actionは以下のJSON形式で定義されます。
{
"type": "OpenApiConnection",
"inputs": {
"parameters": {
"Flow/properties/displayName": "@utcNow()",
"Flow/properties/definition": "@json(variables('payload'))",
"Flow/properties/state": "Started",
"environmentName": "@workflow()?['tags/environmentName']",
"Flow/properties/connectionReferences": [
{
"connectionName": "1419633963d917ba7fa7294fe739d393",
"id": "/providers/Microsoft.PowerApps/apis/shared_flowmanagement"
},
]
},
"host": {
"apiId": "/providers/Microsoft.PowerApps/apis/shared_flowmanagement",
"connection": "shared_flowmanagement",
"operationId": "CreateFlow"
}
},
"runAfter": {
"wait1": [
"Succeeded"
]
},
}
ここからはクラウドフローの作成に関わる parameters
について見ていきます。
Flow/properties/displayName
ここには、新たに作成されるフロー名が設定されます。上の例ではワークフロー式関数を使い、現在時刻(UTC)をフロー名にしています。
Flow/properties/definition
ここには新たに作成するクラウドフローを定義したJSONデータを入力します。ポイントとして、パッケージに含まれる definition.json
とは異なるフォーマットになっています。ここに入力されるフォーマットは以下の形式を満たしている必要があります。triggers
や actions
はパッケージの definitions.json
と同一のフォーマットです。
{
"$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#",
"contentVersion": "1.0.0.0",
"triggers": {
# triggerの設定
},
"actions": {
# actionの設定
},
"parameters": {
"$authentication": {
"defaultValue": {},
"type": "SecureObject"
},
"$connections": {
"defaultValue": {},
"type": "Object"
}
}
}
environmentName
ここにはどのユーザー環境で新たなクラウドフローを作成するを設定します。もし、元のクラウドフローと同一のユーザー環境で作成する場合はワークフロー式関数 @workflow()?['tags/environmentName']
を使います。
Flow/properties/connectionReferences
ここには新しく作成するクラウドフローが用いるConnectorとその接続情報のマッピングを定義します。ここでのフォーマットはここまで説明した connectionReference
と同一のフォーマットを使います。以下は設定するJSONデータの例です。
"connectionReferences": [
{
"id": "/providers/Microsoft.PowerApps/apis/shared_flowmanagement",
"connectionName": "1419633963d917ba7fa7294fe739d393"
}
]
ここまで説明したように parameters
を "Create Flow" Actionへ設定すると、Cloud Flowから新たな任意のCloud Flowを作成できるようになります。
まとめ
今回の記事ではPower Automateのクラウドフローの仕様と定義ファイルを用いたクラウドフローの作成方法を解説しました。今回紹介した仕様の多くはPower AutomateのWeb UI上からは隠され、ユーザーはこれらの知識がなくともワークフローを作成できます。しかし、より細やかなワークフローの制御をする場合やクラウドフローを自動生成する場合には、これらの仕様を理解する必要があります。また、一般的なプログラミング言語ほどドキュメントが整っていない点やJSONとワークフロー式関数を交えた構文が直感的に理解しづらい点はPower Automateの発展的な実装を難しくしています。
冒頭で紹介したPAC2では疑似攻撃を成立させるために、クラウドフローを動的に生成する必要がありました。しかし、クラウドフローを定義したJSONファイルやパッケージファイルを機械的に生成するような機能は提供されていません。そこで新たにクラウドフローをPythonコードで生成できるライブラリを作成しました。次回の記事では、そのPythonライブラリを紹介し、あわせてライブラリをGitHubで公開する予定です。
本記事によってPower Automateへの理解が深まり、Power Automateを活用しているみなさまの一助になれば幸いです。