Skip to content

Commit

Permalink
feat: Added Standard Logic App with Managed Identity and IP restricti…
Browse files Browse the repository at this point in the history
…on (for HTTP trigger)
  • Loading branch information
pipalmic committed Feb 9, 2025
1 parent e5881ee commit bd34ceb
Show file tree
Hide file tree
Showing 3 changed files with 467 additions and 0 deletions.
297 changes: 297 additions & 0 deletions modules/azure/logic_app_standard_http_managed_identity/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,297 @@
terraform {
required_version = "~> 1.3"

required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~> 3.48"
}
archive = {
source = "hashicorp/archive"
version = "~> 2.3"
}
azapi = {
source = "Azure/azapi"
version = "~> 1.4"
}
azuread = {
source = "hashicorp/azuread"
version = "~> 2.36"
}
}

backend "azurerm" {}
}

provider "azurerm" {
features {}
}

provider "archive" {
}

locals {
identity_type = var.use_managed_identity && length(var.identity_ids) > 0 ? "SystemAssigned, UserAssigned" : var.use_managed_identity ? "SystemAssigned" : length(var.identity_ids) > 0 ? "UserAssigned" : null
is_linux = length(regexall("/home/", lower(abspath(path.root)))) > 0
identifiers = concat(["api://${var.managed_identity_provider.create.application_name}"], var.managed_identity_provider.identifier_uris != null ? var.managed_identity_provider.identifier_uris : [])
allowed_audiences = concat(local.identifiers, var.managed_identity_provider.allowed_audiences != null ? var.managed_identity_provider.allowed_audiences : [])
}

resource "azurerm_logic_app_standard" "app" {
name = var.logic_app_name
location = var.location
resource_group_name = var.resource_group_name
enabled = var.enabled
https_only = var.https_only
version = var.logic_app_version

dynamic "identity" {
for_each = local.identity_type != null ? [1] : []
content {
type = local.identity_type
identity_ids = var.identity_ids
}
}

site_config {
ftps_state = "Disabled"
elastic_instance_minimum = var.elastic_instance_minimum
pre_warmed_instance_count = var.pre_warmed_instance_count

dynamic "ip_restriction" {
for_each = var.ip_restrictions

content {
ip_address = ip_restriction.value.ip_address
service_tag = ip_restriction.value.service_tag
virtual_network_subnet_id = ip_restriction.value.virtual_network_subnet_id
name = ip_restriction.value.name
priority = ip_restriction.value.priority
action = ip_restriction.value.action

dynamic "headers" {
for_each = ip_restriction.value.headers

content {
x_azure_fdid = headers.value.x_azure_fdid
x_fd_health_probe = headers.value.x_fd_health_probe
x_forwarded_for = headers.value.x_forwarded_for
x_forwarded_host = headers.value.x_forwarded_host
}
}
}
}
}

app_settings = merge({
WEBSITE_NODE_DEFAULT_VERSION = "~18",
FUNCTIONS_WORKER_RUNTIME = "node",
MICROSOFT_PROVIDER_AUTHENTICATION_SECRET = "${var.managed_identity_provider != null ? azuread_application_password.password[0].value : ""}"
}, var.app_settings)

app_service_plan_id = var.service_plan_id
storage_account_access_key = var.storage_account_access_key
storage_account_name = var.storage_account_name
virtual_network_subnet_id = var.integration_subnet_id
}

# Safest way is to always zip the file, even if there are no changes, it ensures that portal changes do not affect deployment results
resource "null_resource" "zip_logic_app" {
triggers = {
always_run = timestamp()
}
# if check.zip file changes, create deploy.zip file
provisioner "local-exec" {
interpreter = local.is_linux ? ["bash", "-c"] : ["PowerShell", "-Command"]
command = local.is_linux ? "cd ${path.module} && mkdir -p files && cd ${var.workflows_source_path} && zip -rq $OLDPWD/files/deploy.zip ." : "New-Item -Path \"${path.module}\" -Name \"files\" -ItemType \"directory\" -Force; Compress-Archive -Path \"${var.workflows_source_path}\\*\" -DestinationPath \"${path.module}\\files\\deploy.zip\" -Force"
}
}

# After the logic app is created, start a deployment using the Azure CLI
# It is not possible to use a ZIP-deployment from blob storage, as it can not be updated from the portal

# When you add parameters to your logic app using the parameters.json file, and you reference an app setting
# the file will not be accepted if the app setting does not exist. However, there is a small delay between
# updating the logic app and the app settings being available. Therefore, we need to add a timeout to the
# deployment to make sure the app settings are available before the deployment is started.
resource "time_sleep" "wait_for_app_settings" {
depends_on = [
azurerm_logic_app_standard.app,
null_resource.zip_logic_app
]
create_duration = "${var.deployment_wait_timeout}s"
}

# The first step is to ensure that the logic apps extension is installed
resource "null_resource" "install-extension" {
depends_on = [time_sleep.wait_for_app_settings]

provisioner "local-exec" {
command = "az extension add --name logic"
}
}

# Fetch the subscription name
data "azurerm_subscription" "current" {}

# Then use the Azure CLI to start the deployment
resource "null_resource" "deploy" {
depends_on = [
null_resource.install-extension,
null_resource.zip_logic_app
]

triggers = {
always_run = timestamp() # null_resource.zip_logic_app might not always actually change, trigger ensures the execution anyway
}

provisioner "local-exec" {
command = "az logicapp deployment source config-zip --name ${var.logic_app_name} --resource-group ${var.resource_group_name} --subscription ${data.azurerm_subscription.current.display_name} --src ${path.module}/files/deploy.zip"
}
}

data "azurerm_monitor_diagnostic_categories" "diagnostic_categories" {
count = var.log_analytics_workspace_id == null ? 0 : 1
resource_id = azurerm_logic_app_standard.app.id
}

// Write logs and metrics to log analytics if specified
// Needs to be done once the deployment is finished, because updating Diagnostic Settings leads to a restart of the Logic App
// which causes the deployment to fail if it is not finished yet
resource "azurerm_monitor_diagnostic_setting" "diagnostic_setting" {
depends_on = [
null_resource.deploy
]

count = var.log_analytics_workspace_id == null ? 0 : 1
name = "diag-${var.logic_app_name}"
target_resource_id = azurerm_logic_app_standard.app.id
log_analytics_workspace_id = var.log_analytics_workspace_id

dynamic "enabled_log" {
for_each = length(var.log_analytics_diagnostic_categories) > 0 ? var.log_analytics_diagnostic_categories : data.azurerm_monitor_diagnostic_categories.diagnostic_categories[0].log_category_types

content {
category = enabled_log.value

retention_policy {
enabled = false
}
}
}

dynamic "metric" {
for_each = data.azurerm_monitor_diagnostic_categories.diagnostic_categories[0].metrics

content {
category = metric.value
enabled = true

retention_policy {
enabled = false
}
}
}
}

# Managed Identity Provider
data "azuread_client_config" "current" {}

resource "random_uuid" "oath2_uuid" {}

resource "azuread_application" "application" {
count = var.managed_identity_provider != null ? 1 : 0
display_name = var.managed_identity_provider.create.display_name
owners = var.managed_identity_provider.create.owners != null ? concat([data.azuread_client_config.current.object_id], var.managed_identity_provider.create.owners) : [data.azuread_client_config.current.object_id]
sign_in_audience = "AzureADMyOrg"
identifier_uris = local.identifiers

api {
requested_access_token_version = 2

oauth2_permission_scope {
admin_consent_description = var.managed_identity_provider.create.oauth2_settings.admin_consent_description
admin_consent_display_name = var.managed_identity_provider.create.oauth2_settings.admin_consent_display_name
enabled = var.managed_identity_provider.create.oauth2_settings.enabled
id = random_uuid.oath2_uuid.result
type = var.managed_identity_provider.create.oauth2_settings.type
user_consent_description = var.managed_identity_provider.create.oauth2_settings.user_consent_description
user_consent_display_name = var.managed_identity_provider.create.oauth2_settings.user_consent_display_name
value = var.managed_identity_provider.create.oauth2_settings.role_value
}
}

web {
redirect_uris = ["https://${var.logic_app_name}.azurewebsites.net/.auth/login/aad/callback"]

implicit_grant {
access_token_issuance_enabled = false
id_token_issuance_enabled = true
}
}

required_resource_access {
resource_app_id = "00000003-0000-0000-c000-000000000000" # Microsoft Graph

resource_access {
id = "e1fe6dd8-ba31-4d61-89e7-88639da4683d" # User.Read
type = "Scope"
}
}
}

resource "null_resource" "always_run" {
triggers = {
timestamp = "${timestamp()}"
}
}

resource "azapi_update_resource" "setup_auth_settings" {
count = var.managed_identity_provider != null ? 1 : 0
type = "Microsoft.Web/sites/config@2020-12-01"
resource_id = "${azurerm_logic_app_standard.app.id}/config/web"

depends_on = [
azurerm_logic_app_standard.app,
null_resource.always_run
]

body = jsonencode({
properties = {
siteAuthSettingsV2 = {
globalValidation = {
excludedPaths = []
require_authentication = true,
// Even though is looks weird, it is needed. Otherwise, the app and also the designer in Azure Portal are not working
// https://techcommunity.microsoft.com/blog/integrationsonazureblog/trigger-workflows-in-standard-logic-apps-with-easy-auth/3207378
unauthenticatedClientAction = "AllowAnonymous"
},
IdentityProviders = {
azureActiveDirectory = {
enabled = true,
registration = {
clientId = azuread_application.application[0].application_id
clientSecretSettingName = "MICROSOFT_PROVIDER_AUTHENTICATION_SECRET"
},
validation = {
allowedAudiences = local.allowed_audiences
}
}
}
}
}
})
lifecycle {
/* This action should always be replaces since is works under the hood as an api call
* So it does not really track issues with the function app properly
*/
replace_triggered_by = [
null_resource.always_run
]
}
}

resource "azuread_application_password" "password" {
count = var.managed_identity_provider != null ? 1 : 0
application_object_id = azuread_application.application[0].object_id
}
11 changes: 11 additions & 0 deletions modules/azure/logic_app_standard_http_managed_identity/outputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
output "principal_id" {
value = length(azurerm_logic_app_standard.app.identity) > 0 ? azurerm_logic_app_standard.app.identity[0].principal_id : null
}

output "name" {
value = azurerm_logic_app_standard.app.name
}

output "default_hostname" {
value = azurerm_logic_app_standard.app.default_hostname
}
Loading

0 comments on commit bd34ceb

Please sign in to comment.