January 30th, 2025

Using Managed Identity on Logic App consumption

TL;DR

On a recent project, we faced a challenge: deploying an Azure Logic App Consumption workflow that securely connects to other Azure services—like Storage—using Managed Identity. We wanted to avoid the hassle of managing credentials while sticking to our tool of choice, Terraform. But here’s the catch: Terraform doesn’t natively support setting up API connections with Managed Identity for Logic App Consumption workflows.

Instead of settling for manual configurations, we devised a solution. Using Terraform’s azapi_resource for API connections and ARM templates for deploying Logic Apps, we managed to bypass these limitations. Along the way, we set up resource groups, storage accounts, and queues, configured API connections for secure authentication, deployed Logic Apps, and assigned roles to keep everything streamlined. This approach gave us the security and simplicity we needed—without compromising automation.

Problem Statement

While working with Azure Logic App Consumption, we needed to securely connect Logic Apps to Azure services like Storage. Using Managed Identity was the obvious choice: it eliminates the need for hardcoded credentials by letting Azure handle authentication seamlessly.

The problem? Terraform doesn’t yet support configuring API connections with Managed Identity for Logic App Consumption workflows. It’s a known gap in Terraform’s capabilities, as described in its documentation. Specifically, it lacks the ability to directly configure API connections with Managed Identity in Logic App Consumption workflows.

This limitation posed a significant hurdle for our project. We needed a way to deploy Logic Apps that could securely authenticate with Azure services—without resorting to manual steps or insecure workarounds.

Solution

To address this issue, we can leverage Terraform in conjunction with the Azure API to create the necessary resources and configuration for Managed Identity-based authentication in Logic App Consumption. This involves:

  1. Setting up required infrastructure like resource groups, storage accounts, and queues.
  2. Implementing API connections with Managed Identity via Terraform’s azapi_resource since native support is currently limited.
  3. Deploying the Logic App Consumption using azurerm_template_deployment, which will allow us to bypass Terraform’s limitation of directly defining the Logic App with its body.

By following these steps, you can securely deploy Logic App Consumption with Managed Identity for seamless authentication to Azure services without the hassle of managing credentials.

Implementation

  1. Create resource group
    resource "azurerm_resource_group" "rg" {
      name     = local.rg_name
      location = var.location
    }
  2. Set Up a Storage Account and Queue
    resource "azurerm_storage_account" "storage" {
      name                             = var.storage_account_name
      location                         = var.location
      resource_group_name              = azurerm_resource_group.rg.name
      account_tier                     = "Standard"
      account_replication_type         = "GRS"
      public_network_access_enabled    = var.public_network_access_enabled
    }
    
    resource "azurerm_storage_queue" "queue" {
      name                 = local.queue_name
      storage_account_name = azurerm_storage_account.storage.name
    }
  3. Create an API Connection with Managed Identity
    resource "azapi_resource" "create_api_connection" {
      type                      = "Microsoft.Web/connections@2016-06-01"
      name                      = "storage-queue-connection"
      location                  = azurerm_resource_group.rg.location
      parent_id                 = azurerm_resource_group.rg.id
      schema_validation_enabled = false
    
      body = jsonencode({
         properties = {
            displayName = "Queue"
            parameterValueSet = {
               name  = "managedIdentityAuth" #By setting this, we are enforcing Managed Identity on API Connection
               value = {}
            }
            api = {
            name        = "azurequeues"
            displayName = "Azure Queues"
            id          = "/subscriptions/${data.azurerm_subscription.current.subscription_id}/providers/Microsoft.Web/locations/${azurerm_resource_group.rg.location}/managedApis/azurequeues"
            }
         }
      })
    }
  4. Deploy Logic App Consumption Using ARM Template

    Example Logic App Arm Template

      {
      "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
      "contentVersion": "1.0.0.0",
      "parameters": {
         "logic_app_name": {
               "defaultValue": "logicapp-e2e-test-01",
               "type": "String"
         },
         "location": {
               "defaultValue": "westus",
               "type": "String"
         },
         "managed_api_id": {
               "defaultValue": "",
               "type": "String"
         },
         "azurequeue_connection_id": {
               "defaultValue": "",
               "type": "String"
         },
         "storage_account_path": {
               "defaultValue": "",
               "type": "String"
         },
         "trigger_name": {
               "defaultValue": "",
               "type": "String"
         }
      },
      "variables": {},
      "resources": [
         {
               "type": "Microsoft.Logic/workflows",
               "apiVersion": "2017-07-01",
               "name": "[parameters('logic_app_name')]",
               "location": "[parameters('location')]",
               "identity": {
                  "type": "SystemAssigned"
               },
               "properties": {
                  "state": "Enabled",
                  "definition": {
                     "$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#",
                     "contentVersion": "1.0.0.0",
                     "parameters": {
                           "$connections": {
                              "defaultValue": {},
                              "type": "Object"
                           }
                     },
                     "triggers": {
                           "[parameters('trigger_name')]": {
                              "type": "Request",
                              "kind": "Http",
                              "inputs": {
                                 "schema": {
                                       "type": "object",
                                       "properties": {
                                          "id": {
                                             "type": "string"
                                          },
                                          "source": {
                                             "type": "string"
                                          },
                                          "specversion": {
                                             "type": "string"
                                          },
                                          "type": {
                                             "type": "string"
                                          },
                                          "subject": {
                                             "type": "string"
                                          },
                                          "time": {
                                             "type": "string"
                                          },
                                          "data": {
                                             "type": "string"
                                          }
                                       }
                                 }
                              }
                           }
                     },
                     "actions": {
                           "Condition": {
                              "actions": {
                                 "For_each": {
                                       "foreach": "@triggerBody()",
                                       "actions": {
                                          "HTTP_Webhook": {
                                             "type": "HttpWebhook",
                                             "inputs": {
                                                   "subscribe": {
                                                      "method": "POST",
                                                      "uri": "@items('For_each')?['data']?['validationUrl']",
                                                      "body": {
                                                         "validationResponse": "@items('For_each')?['data']?['validationCode']"
                                                      }
                                                   },
                                                   "unsubscribe": {}
                                             }
                                          }
                                       },
                                       "type": "Foreach"
                                 }
                              },
                              "runAfter": {},
                              "else": {
                                 "actions": {
                                       "Put_a_message_on_a_queue_(V2)": {
                                          "type": "ApiConnection",
                                          "inputs": {
                                             "host": {
                                                   "connection": {
                                                      "name": "@parameters('$connections')['azurequeues-1']['connectionId']"
                                                   }
                                             },
                                             "method": "post",
                                             "body": "@triggerBody()?['data']",
                                             "path": "[parameters('storage_account_path')]"
                                          }
                                       }
                                 }
                              },
                              "expression": {
                                 "and": [
                                       {
                                          "equals": [
                                             "@contains(triggerBody()?['data'],'validationUrl')",
                                             "True"
                                          ]
                                       }
                                 ]
                              },
                              "type": "If"
                           }
                     },
                     "outputs": {}
                  },
                  "parameters": {
                     "$connections": {
                           "value": {
                              "azurequeues-1": {
                                 "id": "[parameters('managed_api_id')]",
                                 "connectionId": "[parameters('azurequeue_connection_id')]",
                                 "connectionName": "azurequeues-1",
                                 "connectionProperties": {
                                       "authentication": {
                                          "type": "ManagedServiceIdentity"
                                       }
                                 }
                              }
                           }
                     }
                  }
               }
         }
      ]
    }
    
    data "template_file" "workflow" {
      template = file(var.arm_file_path) # ARM template file path
    }
    
    resource "azurerm_resource_group_template_deployment" "logic_app_workflow_deployment" {
      deployment_mode      = "Incremental"
      name                 = "workflow_deployment"
      resource_group_name  = azurerm_resource_group.rg.name
      parameters_content   = jsonencode({
         "logic_app_name"       = var.logic_app_name
         "location"             = var.location
         "azurequeue_connection_id" = "/subscriptions/${var.subscription_id}/resourceGroups/${azurerm_resource_group.rg.name}/providers/Microsoft.Web/connections/storage-queue-connection"
         "managed_api_id"       = "/subscriptions/${var.subscription_id}/providers/Microsoft.Web/locations/${azurerm_resource_group.rg.location}/managedApis/azurequeues"
      })
      template_content     = data.template_file.workflow.template
    }
  5. Role Assignment for Managed Identity
    resource "azurerm_role_assignment" "logic_app_contributor" {
      scope                = azurerm_storage_account.storage.id
      role_definition_name = "Storage Queue Data Contributor"
      principal_id         = data.azurerm_logic_app_workflow.logic_app.identity[0].principal_id
    }
  6. Extract the Callback URL for the Logic App Trigger (Optional)

    When you need to integrate Logic App via URL, you will need callback_url that has unique path. As we are using arm_template_deployment, we need to extract it via azapi_resource_action. However, if you create Logic app trigger with Terraform, you can also use output from trigger definition.

    data "azapi_resource_action" "callback_url_data" {
      type                   = "Microsoft.Logic/workflows/triggers@2019-05-01"
      action                 = "listCallbackUrl"
      resource_id            = "/subscriptions/${data.azurerm_subscription.current.subscription_id}/resourceGroups/${azurerm_resource_group.rg.name}/providers/Microsoft.Logic/workflows/${local.logic_app_name}/triggers/${local.trigger_name}"
    }

Conclusion

The approach for Managed Identity in Logic App Consumption allowed us to enhance security by eliminating manual credentials, while also creating a scalable and maintainable solution for authentication. Along the way, we navigated the current limitations of Terraform, carefully tuning configurations to make the integration seamless and efficient.

The result was a robust and future-proof setup that not only met the immediate needs of the project but also laid a foundation for secure and scalable infrastructure in the future. This experience underscored the importance of thoughtful architecture in building systems that are secure, maintainable, and ready for growth.

Note: the picture that illustrates this article has been generated by AI on Bing Image Creator.

Author