Azure Logic Appsでパラメーターを扱う

こんにちは、GxPの平山です。
この記事はグロースエクスパートナーズ Advent Calendar 2022の18日目です。

https://qiita.com/advent-calendar/2022/gxp

現在携わっている案件にてAzureを扱っており、[Azure Logic Apps](https://learn.microsoft.com/ja-jp/azure/logic-apps/logic-apps-overview)を使用しております。
今回はLogic Appsのワークフローでパラメーターを扱う方法の紹介と、注意点、応用についてご紹介したいと思います。
Logic Appsを複数の環境で扱うような場合に、パラメーターを活用することで環境に合わせて動作を制御できて便利です。

※本記事内の検証内容は、Logic Appsの従量課金タイプ、ホスト環境がマルチテナントでの検証になります。

# Logic Appsのパラメーター機能を使用する

Logic Appsが提供するパラメーター機能について紹介いたします。まずは基本から。
手順については以下に記載がありますが、細かい部分や、実際の画面内容も添えて説明いたします。

https://learn.microsoft.com/ja-jp/azure/logic-apps/create-parameters-workflows?tabs=consumption#define-use-and-edit-parameters

## パラメーターの定義
Logic Apps のデザイナー上で「[@]パラメーター」を選択し、「パラメーターの追加」をクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2247089/7b504e53-4ce7-5665-6e52-c1bd52cafc48.png)

以下のような画面が表示され、「パラメーターの名前」、「種類」、「既定値」を入力します。
「実際の値」についてはグレーアウトされており、入力ができません（後述の「[パラメーターの実際の値を設定する](#パラメーターの実際の値を設定する)」にて説明します）。
今回は外部のAPIを呼び出すようなケースを想定して、APIのURLをパラメーター化してみます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2247089/952b3709-3a0c-df2c-38f3-6bdde9422cff.png)

## パラメーターを使用する
ワークフロー上の設定したい項目を選択すると、前節で定義したパラメーターが表示されます。
以下のキャプチャでは、「URI」欄を選択している状態です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2247089/49c709bb-6d83-1933-a403-fbb3519fe147.png)

パラメーターを選択することで、「URI」欄にパラメーターが設定されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2247089/af2cb92c-fd68-2f69-8b8d-b1a9c45e84d3.png)

この状態で保存の上、Logic Appsを実行すると、先ほど設定したパラメーターの値（既定値）が展開され、http://example.com にGETリクエストが実行されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2247089/7cacfa7a-ebac-6112-531d-acca19b1cd17.png)


## パラメーターの実際の値を設定する

先ほど定義したパラメーターの値はあくまで既定値（デフォルト）です。
環境などに合わせてパラメーターを変更したい場合は、「実際の値」を設定します。
「実際の値」は、Logic Appsのコードエディタ上で指定できます。

先ほどの`HogeApiURI`パラメーターの「実施の値」を、 http://www.example.net にしてみます。
``` diff_json
{
    "definition": {
        "$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#",
        "actions": {
            "HTTP": {
                "inputs": {
                    "method": "GET",
                    "uri": "@parameters('HogeApiUrl')"
                },
                "runAfter": {},
                "type": "Http"
            }
        },
        "contentVersion": "1.0.0.0",
        "outputs": {},
        "parameters": {
            "HogeApiUrl": {
                "defaultValue": "http://example.com",
                "type": "String"
            }
        },
        "triggers": {
            "manual": {
                "inputs": {
                    "schema": {}
                },
                "kind": "Http",
                "type": "Request"
            }
        }
    },
-   "parameters": {}
+   "parameters": {
+       "HogeApiUrl": {
+           "value": "http://www.example.net"
+       }
    }
}
```
変更内容について解説しておくと、json のトップレベルの`parameters`に対して、Logic Appsのデザイナーで定義したパラメーターと同じ名前のプロパティを追加し、`value`プロパティの値を指定します
なお、デザイナーで定義したパラメーターは、`definition`の`parameters`プロパティに種類（`type`）と既定値（`defaultValue`）が記載されています
1つのjson内に`parameters`が2つ出てくるので、違いに注意してください。

再度、デザイナーでパラメーターを確認してみると、「実際の値」欄に http://www.example.net が設定されています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2247089/0d227ef5-158d-9c2b-2772-3e0e29716169.png)

実行してみると、「既定値」ではなく、「実際の値」に対してGETリクエストが実行されています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2247089/5ee30737-dff5-c0dc-d9d7-ed714d6b7755.png)

## パラメーターの種類と値サンプル
最後に、LogicAppsのパラメーターの種類と値のサンプルを照会しておきます。いずれも素直な構文なので、悩むことはないと思いますが、参考までに。

| 種類 | 値のサンプル |
|:-:|:-:|
| String  | "test value" |
| Array  | [1,2,3] |
| Bool  | true |
| Float  | 1.234 |
| Int  | 123  |
| Object  | {"column1":"value1", "column2":"value2"}  |
| Secure String  | ※String型と同様  |
| Secure Object  | ※Object型と同様  |

なお、Secure *型を使用する場合ですが、**「既定値」には必ずダミー値を設定してください！**
型の種類にかかわらず、「既定値」はプレーンテキストとして保存されるため、他人が閲覧できる状態となるためです。
既定値には何らかのダミー値を入れて置きましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2247089/2aef9755-e8e7-3321-40d3-820e66cfdf2f.png)
※「設定しないことをお勧めします」と出ていますが、値を入れないと保存できません…。

「[パラメーターの実際の値を設定する](#パラメーターの実際の値を設定する)」で紹介した方法で「実際の値」を指定してください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2247089/3808aa07-2607-3a91-7045-5d7b9ea4ac13.png)
設定を保存したのち、画面のリロードを行うと見えないようになります。
これは、対応するパラメーターがSecure *型であることをLogic Appsが判別して、値を隠蔽してくれるようです 。 
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2247089/f415e17e-fa7c-d2b0-1252-2d5d753e6a3f.png)
参考：[ワークフロー定義内のパラメーターをセキュリティで保護する](https://learn.microsoft.com/ja-jp/azure/logic-apps/logic-apps-securing-a-logic-app?tabs=azure-portal#secure-parameters-workflow)


# Azure Key Vaultで定義したシークレットを取得する
Logic Appsの Secure *型パラメーターを使えば、秘匿情報を安全に扱うことができますが、管理が個々のLogic Appsに分散してしまいます。
Azureで秘匿情報を一元管理するサービスといえば、Azure Key Vaultですね。

Azureでは、Key Vault のシークレットをLogic Appsで扱うための、ビルトインコネクタが提供されています。
これを使えば、Key Vaultで管理されたシークレットを、簡単な手順で参照可能です。

https://learn.microsoft.com/ja-jp/connectors/keyvault/

## Logic Apps の設定手順
Azure Key Vault のコネクタを選択し、「シークレットの取得」アクションを選択します
初期画面が表示されたら、まずはKey Vaultのリソース名を入力します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2247089/f7864236-f59b-c368-4358-fa447cd150e8.png)

Key Vaultへのアクセスは認証が必要です。推奨はマネージドIDでの認証です。
「マネージドIDを使用して接続する」を選択し、接続名と再度Key Vaultのリソース名を設定します。システム割り当てマネージドIDで作成を実行します（API 接続リソースが作成されます）。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2247089/a68dcd8d-e375-f586-056f-947c251e4034.png)

シークレットの取得の設定画面に戻ったところで、取得したいシークレット名を入力します
入力しようとすると「値を取得できませんでした。（以下略）」と出てます。
あえてKey Vaultのシークレットに対する「一覧」の権限をつけていないためです。
「カスタム値の入力」か、コードエディタで直接入力できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2247089/546538f3-6975-7a19-a15d-222012261787.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2247089/dad34788-a8f8-20b6-2d39-e8da94e688f7.png)

Logic Appsでパラメーターを定義した場合と同様に、後続のステップで参照できるようになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2247089/6b808690-d1b4-8ed6-88c1-2eb605839d5f.png)

## :warning: シークレットを扱う上での注意事項
**今回の個人的には一番伝えておきたいところです。案外見落としがちなところだと思います（私だけでしょうか…？）。**

まずは以下を見てみてください。HTTPコネクタで外部のAPIを呼び出すシナリオを想定して、LogicAppsを実行した結果（ログ）です。
Key Vaultから取得したシークレット（APIキー）を、`x-api-key`ヘッダに指定して呼び出しています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2247089/88fbc9c5-2c90-680f-170d-a28668012530.png)
お　か　わ　り　い　た　だ　け　る　だ　ろ　う　か　？　:rice: 
そうです、<b>ログにシークレット情報が表示されちゃってます。</b>やばいですね！

このように、何も考えず後続のステップで取得したシークレットを参照すると、ログに記録されてしまう場合があります。
Logic Appsで定義したSecure *型のパラメーターを定義して使用する場合も同様です。

公式ドキュメントでも、このようなケースについて以下のように注意喚起されています。ちゃんと対策しましょう。
> ロジック アプリの実行履歴を表示するときは、Azure Logic Apps によってアクセスの認証が行われた後、各実行の要求と応答に対する入力および出力へのリンクが提供されます。 ただし、パスワード、シークレット、キーなどの秘匿性の高い情報を処理するアクションについては、他のユーザーがそのデータを表示したり利用したりできないようにします。 たとえば、ロジック アプリが HTTP アクションの認証時に使用する Azure Key Vault のシークレットを取得する場合、そのシークレットが見えないようにする必要があります。

https://learn.microsoft.com/ja-jp/azure/logic-apps/logic-apps-securing-a-logic-app?tabs=azure-portal#access-to-run-history-data

対策方法、設定手順についても上記の公式ドキュメントに書かれていますので、ご参照ください。
本記事では、実際に設定してみた結果をご紹介します。それぞれメリット/デメリットがありますので、ご利用の状況に合わせて選んでいただくとよいかと思います。

### [対策1] IP アドレス範囲でアクセスを制限する
IPアドレスでの制限（ホワイトリスト形式）です。許可されていないIPアドレスからのアクセスだと、すべての実行ログが見えなくなります。これでもよいですが、関係ないところまで見えなくなってしまうのと、リモートワークが当たり前な昨今を考えると、IPアドレスでの制御は少々扱いづらいかもしれません。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2247089/bc2d959d-421a-256d-8dc0-3d06fccf6da6.png)

### [対策2] 難読化を使用して実行履歴のデータをセキュリティで保護する
「HTTP」の入力の保護設定を有効化することで、保護すべき部分だけ見えなくできます。個人的にはこちらのほうがオススメです。
この方法では見えてもよいユーザーも見えなくなりますが、ログに秘匿情報が残ってしまうのは予期せぬ漏洩のリスクを孕むので、表示させないほうが安全です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2247089/088b8a54-8e30-4ac2-648d-ddcfd7a90e48.png)
※「シークレットの取得」の出力の保護設定は無効のままですが、シークレットのメタデータのみ出力される（値は出力されない）ので許容範囲と判断しました。必要であればこちらにも保護をかけておきましょう。

# AppConfiguration上で定義したパラメーター（キー値）を取得する
App Configuration でアプリケーションの構成を一元管理しているような場合、LogicAppsで扱うパラメーターも管理したくなるかもしれません。
App Configuration ではキーと値のセットで構成管理を行うので、Key Vaultと同様の方法で取得ができそうですが…。
残念ながら、現時点でApp Configuration 用のコネクタは公式から提供されていません。
ということで、公式がサポートしてくれるのを待ちましょう…。


……だと面白くないので、『ないなら作ればいいじゃない』の精神で、自分で作ってみます。
このようなときに使える手段としては、カスタムコネクタという機能があります。

https://learn.microsoft.com/ja-jp/connectors/custom-connectors/

> Azure Logic Apps、Microsoft Power Automate、Microsoft Power Apps には、Microsoft および検証済みサービスに接続するための 750 個のコネクタ 以上が用意されていますが、あらかじめ構築されているコネクタが利用できないサービスとの通信が必要になることもあります。 カスタム コネクタでは、独自のトリガーとアクションを備えたコネクタを作成 (さらには共有) できるようにすることで、このシナリオに対応しています。

ということで、今回の要件にはちょうど良さそうですね。

## App Configuration のキー値作成

App Configurationで以下のようなキー値を用意しました。環境別にパラメータを分けているようなイメージです。

| Key | 値 |
|:-:|:-:|
| LogicAppsTestParameter:Develop | 開発用のパラメーター |
| LogicAppsTestParameter:Production | 本番用のパラメーター |

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2247089/e3c0c8b5-f115-ddb9-968b-482dc07c3394.png)

## カスタムコネクタ用REST API作成

カスタムコネクタはREST APIのラッパー(Logic AppsはSOAP APIもサポート)なので、App Configurationからキー値を取得して返すREST APIを用意します
比較的シンプルな処理なので、今回はAzure Functions（HTTPトリガー）で実装してみます。

https://learn.microsoft.com/ja-jp/azure/azure-functions/functions-bindings-http-webhook-trigger?tabs=in-process%2Cfunctionsv2&pivots=programming-language-csharp

Azure Functionsのリソース作成、デプロイに関しては本稿内では割愛させていただきますが、以下を参考にしていただくとよいかと思います。

https://learn.microsoft.com/ja-jp/azure/azure-functions/create-first-function-vs-code-csharp?tabs=in-process

### Azure Functionsで実行するコード
``` csharp
public class GetAppConfigurationKeyValue
{
    private readonly ILogger<GetAppConfigurationKeyValue> _logger;

    public GetAppConfigurationKeyValue(ILogger<GetAppConfigurationKeyValue> log)
    {
        _logger = log;
    }

    [FunctionName("GetAppConfigurationKeyValue")]
    [OpenApiOperation(operationId: "Run", tags: new[] { "name" })]
    [OpenApiSecurity("function_key", SecuritySchemeType.ApiKey, Name = "code", In = OpenApiSecurityLocationType.Query)]
    [OpenApiParameter(name: "AppKey", In = ParameterLocation.Header, Required = true, Type = typeof(string), Description = "App Configuration Key")]
    [OpenApiResponseWithBody(statusCode: HttpStatusCode.OK, contentType: "text/plain", bodyType: typeof(string), Description = "The OK response")]
    public async Task<IActionResult> Run(
        [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req)
    {
        _logger.LogInformation("C# HTTP trigger function processed a request.");
        // Extract the Config Key from the Request header
        string appKey = req.Headers["AppKey"].FirstOrDefault();
        // Extract the Connection String from the Function App Settings
        string connectionString = System.Environment.GetEnvironmentVariable("AppConfigConnectionString");
        var builder = new ConfigurationBuilder();
        builder.AddAzureAppConfiguration(connectionString);
        var build = builder.Build();
        string message = build[appKey];
        return message == null
                ? new NotFoundObjectResult("Azure Configuration Key not found - " + appKey)
                : new OkObjectResult(message);
    }
}
```

Functionsにコードをデプロイしたら、関数アプリの構成設定で、App Configurationへの接続文字列を設定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2247089/8b303964-2e0d-8eb4-57e7-434abeb7ff88.png)
設定ができたら、Functionsのポータル画面上でテストを実行してみます（`LogicAppsTestParameter:Develop`を取得してみます）。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2247089/34baf9a2-d690-a8e5-6812-ef78bf975b0d.png)
ちゃんと取れました。これでAPIの準備は完了です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2247089/cad6c3fa-f70c-c47d-818c-8b6fa9c074d2.png)

### API Specの取得
以下のURLにアクセスすると、API Specが確認できます。こちらの内容をローカルにファイル保存しておきます（カスタムコネクタの設定にて使用します）。
https://<I>{Azure Function Name}</I>.azurewebsites.net/api/swagger.json
※カスタムコネクタはOpenAPI 3.0 スキーマ定義はサポートされていないため、Swagger 2.0 を取得してください（早くサポートしてほしいですね…）。

<details><summary><b>（参考）今回のサンプルで取得できたAPI Spec</b></summary><div>

``` json
{
  "swagger": "2.0",
  "info": {
    "title": "OpenAPI Document on Azure Functions",
    "version": "1.0.0"
  },
  "host": "*************************.azurewebsites.net",
  "basePath": "/api",
  "schemes": [
    "https"
  ],
  "paths": {
    "/GetAppConfigurationKeyValue": {
      "get": {
        "tags": [
          "name"
        ],
        "operationId": "Run",
        "produces": [
          "text/plain"
        ],
        "parameters": [
          {
            "in": "header",
            "name": "AppKey",
            "description": "App Configuration Key",
            "required": true,
            "type": "string"
          }
        ],
        "responses": {
          "200": {
            "description": "The OK response",
            "schema": {
              "type": "string"
            }
          }
        },
        "security": [
          {
            "function_key": [ ]
          }
        ]
      }
    }
  },
  "securityDefinitions": {
    "function_key": {
      "type": "apiKey",
      "name": "code",
      "in": "query"
    }
  }
}
```
</div></details>

## カスタムコネクタを作成
下準備ができたところで、いよいよ本題です。カスタムコネクタを作成します。
リソースの作成については、特別触れておくようなところはないですね（名前を入力するぐらい）。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2247089/9c9eb28c-ae5a-6ff4-53af-d670bc3a3eb5.png)

リソース作成が済んだら、編集を行います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2247089/9cae6bbe-689d-a5ae-ed61-1a88278eff11.png)

全般画面にて、「[API Specの取得](#api-specの取得)」で取得したファイルをインポートします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2247089/4ba0a401-3aae-65ec-9503-fba9b71702d9.png)

独自のアイコンを設定できたりします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2247089/aa49a8b5-48c6-c26a-b2c6-0e7b31165ac6.png)

セキュリティ画面ですが、インポートしたAPI Specによって設定が反映されています。 
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2247089/438e6ffb-54cc-6547-7350-b41f52995d86.png)

「概要」を入力して、「コネクタの更新」を実行すれば作成完了です（他の内容については割愛させていただきます）。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2247089/9a7ab3f6-6e7e-a57d-9678-fdb873e31f56.png)

## Logic Appsでカスタムコネクタを設定
準備が整ったところで、Logic Appsでカスタムコネクタを使用します。

Logic Appsのデザイナーで、カスタムタブを選択すると、作成したカスタムコネクタのアクションが出てきました！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2247089/0ec59ee6-367b-d485-ceef-f6073da21151.png)

API（今回はAzure Functions）リクエスト時のAPIキーを設定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2247089/d8f631ed-3d0b-95d4-8b0a-be114f2369da.png)

ここまでで下準備は整いました！
早速「[App Configuration のキー値作成](#app-configuration-のキー値作成)」で設定した値を取得してみます。
`LogicAppsTestParameter:Develop` の値を取得する想定として、`Environment`パラメーターに既定値として「Develop」を設定しています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2247089/92c80384-f6f1-098c-6360-ac818054463e.png)

取得できました！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2247089/fb3628e9-f7c6-1c5c-a4cc-025724d3f05e.png)

`Environment`パラメーターの「実際の値」を「Production」に変更することで、本番のパラメーターも取得できました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2247089/8daef55c-7a82-ad75-c6aa-632cc791883c.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2247089/040831f9-b291-e58a-1cc7-e68b9288f0bf.png)

# まとめ
Logic Appsでパラメーターを扱う方法について、いくつか紹介させていただきました。
今回はシークレットの扱いに関する注意点について書きたいと思ったのがきっかけでした。
それだけだと内容的に物足りないなと感じ、App Configurationからパラメータを取得する方法を検証してみましたが、思いのほか長くなってしまいました…。
カスタムコネクタは案件でも使用したことのない機能だったので、今回試せてよかったです。

ちなみにLogic AppsではAzure Functions用のビルトインコネクタが使用できるので、今回のケースだと実はカスタムコネクタを使用しなくても構成は可能です。
カスタムコネクタの魅力としては、対象となるAPIがAzure内外を問わずラップでき、デザイナー上でユーザーフレンドリーな操作感を提供できるところにあると感じました。
プログラミングができなくとも、ワークフローが構築できるLogic Apps の魅力を引き上げる要素の一つだなと理解しました。

# 参考文献

http://www.nehaascentltd.com/2019/08/14/azure-app-configuration-with-logic-apps/