Skip to content

libre-devops/terraform-azurerm-logic-app-workflow

Repository files navigation

Terraform Azure Logic App Workflow

Consumption Logic App workflow shells with typed parameters, shared API connections, access control, and the hidden-title tag, per the Libre DevOps Logic App standard.

CI Release Terraform Registry License


Overview

The module owns the workflow SHELL; content (triggers and actions) is authored as raw azurerm_logic_app_trigger_* / azurerm_logic_app_action_* resources against the module's ids, per the Libre DevOps Logic App standard: content graphs are per-playbook and separate resources give explicit depends_on ordering.

What the shell earns:

  • Typed parameters, the no-hardcoding path: one entry generates both the workflow parameter definition (type, defaultValue, allowedValues, description) and its deployment value; content only ever says @parameters('name'). Secure types cannot carry defaults or allowed values (definitions are Reader-visible), every parameter must have a value or default, and values are type-checked at plan time.
  • Connections declared once: shared_connections puts the estate's common API connections (the usual Sentinel/Monitor/ITSM set) on every workflow; per-workflow connections merge over them by key, and use_shared_connections = false opts out. The module generates the entire $connections parameter pair, including the ManagedServiceIdentity authentication block, byte compatible with @parameters('$connections')['<api>']['connectionId'] references.
  • Diagnostics declared once: diagnostics gives every workflow an allLogs diagnostic setting named diag-<workflow>, with per-workflow override and opt-out.
  • The hidden-title tag is required: title becomes the portal subtitle, as the standard mandates.
  • Access control first-class: per-plane IP restrictions and AAD open authentication policies on the trigger; a check flags workflows whose triggers are SAS-only.

The retired Integration Service Environment is deliberately not exposed. The resource group is passed by id and parsed.

Usage

locals {
  # The playbook catalog: one entry per workflow shell; content lives in per-playbook files.
  playbooks = {
    "logic-ldo-uks-prd-001" = {
      title = "Sentinel Incident - MDO incidents to ServiceNow"
    }
    "logic-ldo-uks-prd-002" = {
      title = "HTTP - Summarise a Sentinel alert storm and notify on-call"
      parameters = {
        storm_threshold = { type = "Int", value = var.storm_threshold }
      }
    }
  }
}

module "playbooks" {
  source  = "libre-devops/logic-app-workflow/azurerm"
  version = "~> 4.0"

  resource_group_id = module.rg.ids["rg-ldo-uks-prd-001"]
  location          = "uksouth"
  tags              = module.tags.tags

  shared_connections = {
    "azuresentinel" = {
      connection_id         = azapi_resource.sentinel_connection.id
      managed_api_id        = data.azurerm_managed_api.sentinel.id
      managed_identity_auth = true
    }
  }
  diagnostics = { log_analytics_workspace_id = module.law.workspace_ids["log-ldo-uks-prd-001"] }

  workflows = local.playbooks
}

Examples

  • examples/minimal - one shell plus the standard's content shape: a recurrence trigger and a Compose action proving @parameters() end to end.
  • examples/complete - the alert-storm playbook: a Sentinel-onboarded workspace, our own Sentinel API connection (azapi, managed identity auth) as the shared estate default, a scheduled query rule counting incident volume, an action group whose logic app receiver fires the workflow's HTTP trigger, and content with verbose names, descriptions on every action, an explicit exponential retry policy, and a Scope try/catch.

Developing

Local work needs PowerShell 7+ and just, because the recipes wrap the LibreDevOpsHelpers PowerShell module (the same engine the libre-devops/terraform-azure action runs in CI). Install just with brew install just, or uv tool add rust-just then uv run just <recipe>.

Run just to list recipes: just update-ldo-pwsh (install or force-update LibreDevOpsHelpers from PSGallery), just validate, just scan (Trivy only), just pwsh-analyze (PSScriptAnalyzer only), just plan, just apply, just destroy, just e2e, just test, and just docs (the plan/apply/destroy recipes mirror the action, including the storage firewall dance; just e2e applies an example then always destroys it, defaulting to minimal, so nothing is left running). Releasing is also just: just increment-release [patch|minor|major] bumps, tags, and publishes a GitHub release, and the Terraform Registry picks up the tag.

Security scan exceptions

This module is scanned with Trivy; HIGH and CRITICAL findings fail the build. Any waiver is a deliberate, reviewed decision, never a way to quiet a finding that should be fixed. Waivers live in .trivyignore.yaml (the machine-applied source of truth, passed to Trivy with --ignorefile) and are mirrored in the table below so the reason is auditable.

Trivy ID Resource Finding Justification
None

To add an exception: add an entry to .trivyignore.yaml (id, optional paths to scope it, and a statement recording why), then add a matching row here. Where the finding is out of this module's scope, point the justification at the Libre DevOps module that does address it (for example the private-endpoint module). Both the file and this table are reviewed in the pull request.

Reference

The Requirements, Providers, Inputs, Outputs, and Resources below are generated by terraform-docs.

Requirements

Name Version
terraform >= 1.9.0, < 2.0.0
azurerm >= 4.0.0, < 5.0.0

Providers

Name Version
azurerm >= 4.0.0, < 5.0.0

Modules

No modules.

Resources

Name Type
azurerm_logic_app_workflow.this resource
azurerm_monitor_diagnostic_setting.this resource

Inputs

Name Description Type Default Required
diagnostics Module-level default diagnostics: when set, every workflow gets an allLogs diagnostic setting to this workspace (named diag-) unless it sets its own diagnostics or opts out with diagnostics_enabled = false. An object rather than a bare string so its presence stays plan-known when the workspace is created in the same apply (for_each keys must never depend on unknown values).
object({
log_analytics_workspace_id = string
})
null no
location Azure region for the workflows. string n/a yes
resource_group_id Resource id of the resource group the workflows are created in. The resource group name and subscription are parsed from this id. string n/a yes
shared_connections API connections shared by every workflow in the call (the usual estate shape: the same Sentinel/Monitor/ITSM connections on every playbook), keyed by managed API name. Merged under each workflow's own connections (same-key workflow entries win); a workflow opts out with use_shared_connections = false.
map(object({
connection_id = string
connection_name = optional(string)
managed_api_id = string
managed_identity_auth = optional(bool, false)
}))
{} no
tags Tags applied to the workflows (merged with per-workflow tags; the workflow's title always lands as the hidden-title tag). map(string) {} no
workflows Consumption Logic App workflows (Microsoft.Logic/workflows) keyed by name
(logic-ldo-uks-prd-001). The workflow content (triggers and actions) is authored as raw
azurerm_logic_app_trigger_* / azurerm_logic_app_action_* resources per the Libre DevOps Logic
App standard; this module owns the shell. Fields:
title REQUIRED human-readable description, becomes the hidden-title tag the portal
renders as the resource subtitle ("{Trigger type} - {what it does}").
parameters Typed workflow parameters, the no-hardcoding path. Each entry generates BOTH the
workflow parameter definition (type, defaultValue, description, allowedValues)
AND its deployment value, so content only ever references
@parameters('name'). Types: String, SecureString, Int, Float, Bool, Object,
Array, SecureObject. Values are strings (Terraform coerces numbers and bools, so
value = 25 works; Object and Array values pass jsonencode(...), the same rule
the standard sets for all workflow JSON); the module converts defaults and
allowed values back to their real JSON types in the definition. Secure types
must not set default or allowed_values (their definitions are readable by anyone
with Reader).
identity SystemAssigned by default; UserAssigned supported.
connections API connections for the workflow's managed connectors, keyed by the managed API
name the action bodies reference. Generates the entire $connections parameter
pair (definition and value: connectionId, connectionName, id, and the
ManagedServiceIdentity authentication block when managed_identity_auth is true),
so bodies keep saying
@parameters('$connections')['']['connectionId'] with zero hand-rolled
JSON.
diagnostics Optional per-workflow diagnostic setting (allLogs) to a Log Analytics workspace,
named diag- unless overridden.
access_control IP restrictions for action/content/workflow_management, and for the trigger
additionally AAD open authentication policies (claims like iss/aud/appid), the
right way to let an action group or app call an HTTP trigger without shared
SAS exposure.
enabled, workflow_schema, workflow_version, integration_account_id Pass-throughs.
The retired Integration Service Environment (integration_service_environment_id) is
deliberately not exposed.
map(object({
title = string
enabled = optional(bool, true)
tags = optional(map(string))

identity = optional(object({
type = optional(string, "SystemAssigned")
identity_ids = optional(set(string))
}), {})

parameters = optional(map(object({
type = string
value = optional(string)
default = optional(string)
description = optional(string)
allowed_values = optional(list(string))
})), {})

connections = optional(map(object({
connection_id = string
connection_name = optional(string)
managed_api_id = string
managed_identity_auth = optional(bool, false)
})), {})
use_shared_connections = optional(bool, true)

diagnostics = optional(object({
log_analytics_workspace_id = string
name = optional(string)
}))
diagnostics_enabled = optional(bool, true)

access_control = optional(object({
action_allowed_ips = optional(list(string))
content_allowed_ips = optional(list(string))
workflow_management_allowed_ips = optional(list(string))
trigger = optional(object({
allowed_ips = optional(list(string), [])
open_authentication_policies = optional(map(object({
claims = map(string)
})), {})
}))
}))

workflow_schema = optional(string)
workflow_version = optional(string)
integration_account_id = optional(string)
}))
{} no

Outputs

Name Description
access_endpoints Map of workflow name to its access endpoint.
connector_outbound_ip_addresses Map of workflow name to the outbound IPs its managed connectors call from (for allow-listing on downstream firewalls).
identities Map of workflow name to its identity { principal_id, tenant_id } (principal_id is populated for system-assigned identities), for role assignments.
ids Map of workflow name to its resource id.
ids_zipmap Map of workflow name to a { name, id } object, for passing where both are needed together.
names The workflow names.
resource_group_name Resource group name parsed from resource_group_id.
subscription_id Subscription id parsed from resource_group_id.
tags The base tags applied to the workflows.
workflow_outbound_ip_addresses Map of workflow name to the outbound IPs the workflow runtime calls from (for allow-listing on downstream firewalls).

About

πŸ”„ Consumption Logic App workflow shells: typed parameters, shared connections, access control, hidden-title, per the Libre DevOps standard

Topics

Resources

License

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors