Zum Hauptinhalt springen
tsecurity.de LIVE
Echtzeit-Radar & Feeds
Alle RSS Feeds
👥 Community & Social
IT NachrichtenTelekom: Neuer Reise-eSIM-Dienst T-Travel startet weltweit(21.09.2026 um 11:45 Uhr)
IT NachrichtenSamsung Wallet: Neue Banken mit an Bord(21.09.2026 um 13:07 Uhr)
IT NachrichtenTelekom: Neuer Reise-eSIM-Dienst T-Travel startet weltweit(21.09.2026 um 11:45 Uhr)
IT NachrichtenSamsung Wallet: Neue Banken mit an Bord(21.09.2026 um 13:07 Uhr)
Intelligence View
⚡ tsecurity.de Intelligence

Securing Shared API Connections in Azure Logic Apps Standard with Access Policies

The Problem In an enterprise integration platform built on Azure Logic Apps Standard, you will almost always end up with multiple Logic Apps in the same resource group — each handling a different integration domain. It is also common to s…

0
↗ Quelle (dev.to)
Reagiere als Erste:r — dein Feedback zählt!




The Problem



In an enterprise integration platform built on Azure Logic Apps Standard, you will almost always end up with multiple Logic Apps in the same resource group — each handling a different integration domain. It is also common to share infrastructure: a single Office 365 managed connection, a single on-premises data gateway connection, or a set of SFTP connections that several Logic Apps legitimately need.



This creates two design tensions:





  1. Security: You do not want Logic App A to be able to use a managed connection intended only for Logic App B.


  2. Operability: You do not want to provision a separate copy of every connection for every Logic App — that duplicates infrastructure and doubles your maintenance burden.



The answer is API Connection Access Policies combined with Managed Identity. You provision each managed connection once, shared, and then control which Logic App can authenticate to it at the ARM level — not at the network or key level.









Background: How Managed API Connections Work in Logic Apps Standard



Logic Apps Standard uses two kinds of connections:























Type Example Authentication

Managed API connection (Microsoft.Web/connections)
Office 365, SFTP, on-premises filesystem OAuth / Managed Identity via ARM

Service provider connection (built-in)
Service Bus, Azure Blob, FTP App settings (connection string or credential)


This article focuses on managed API connections. These are full ARM resources in your resource group. When a Logic App calls a managed connector at runtime, it presents the Managed Identity token of the Logic App app service and ARM validates whether that identity is authorised to use that specific connection. This is controlled by an access policy — a child resource of Microsoft.Web/connections.



If no access policy exists for the calling identity, the connection call fails at runtime even if the connection itself is healthy.









The Pattern: One Connection, Multiple Logic Apps, Granular Access






Architecture Overview






Resource Group

├── office365-conn (Microsoft.Web/connections)
│ └── accessPolicies/
│ └── policy-logicapp-billing ← only billing LA can call this

├── sftp-partner-conn (Microsoft.Web/connections)
│ └── accessPolicies/
│ ├── policy-logicapp-inbound ← inbound LA can call this
│ └── policy-logicapp-outbound ← outbound LA can call this

├── logicapp-billing (Microsoft.Web/sites, kind: workflowApp)
│ └── System-assigned identity: xxxxxxxx

├── logicapp-inbound (Microsoft.Web/sites, kind: workflowApp)
│ └── System-assigned identity: yyyyyyyy

└── logicapp-outbound (Microsoft.Web/sites, kind: workflowApp)
└── System-assigned identity: zzzzzzzz






Key points:





  • office365-conn is shared infrastructure — provisioned once.


  • sftp-partner-conn is also shared but accessible by two Logic Apps.

  • Each Logic App has its own system-assigned Managed Identity.

  • Access policies tie a specific identity to a specific connection. The billing Logic App cannot call the SFTP connection because there is no access policy for it on that connection.









Provisioning Connections (Shared Infrastructure)



Connections are provisioned via Bicep and deployed once, idempotently. Because multiple Logic Apps may need the same connection, this step is decoupled from individual Logic App deployments — it runs as a shared infrastructure step.




resource office365Conn 'Microsoft.Web/connections@2016-06-01' = {
name: 'office365-conn'
location: location
properties: {
displayName: 'office365-conn'
api: {
id: subscriptionResourceId('Microsoft.Web/locations/managedApis', location, 'office365')
}
parameterValues: {
// credentials injected from pipeline variable group
}
}
}






All connections in the platform are provisioned in a single Bicep deployment with --mode Incremental — meaning re-running it is safe and does not disrupt existing connections or their existing access policies.









Assigning Access Policies Per Logic App



This is the key step that enforces isolation. Each Logic App deployment pipeline runs the following sequence as part of its deployment.






Step 1 — Fetch the Logic App's Managed Identity



The Logic App must be provisioned first so that Azure has assigned it a system-assigned Managed Identity. Because the identity may take a few seconds to propagate after provisioning, the pipeline polls with retries:




$maxAttempts = 10
$delaySeconds = 15
$laPrincipal = $null

for ($i = 1; $i -le $maxAttempts; $i++) {
$laPrincipal = az logicapp show `
-g $resourceGroup `
--name $logicAppName `
--query identity.principalId `
--output tsv

if (-not [string]::IsNullOrWhiteSpace($laPrincipal)) { break }

Write-Host "Attempt $i/$maxAttempts — identity not yet available, retrying in ${delaySeconds}s..."
Start-Sleep -Seconds $delaySeconds
}

if ([string]::IsNullOrWhiteSpace($laPrincipal)) {
Write-Error "Managed identity did not become available after $($maxAttempts * $delaySeconds)s."
exit 1
}






This principal ID is the object ID of the Logic App's Managed Identity in Azure AD. It is passed forward as a pipeline variable for the next step.






Step 2 — Prepare the Access Policy Body



The access policy document is stored as a template file, with the principal ID and tenant ID injected by the pipeline's token replacement task:




{
"type": "Microsoft.Web/connections/accessPolicy",
"location": "#{var_location}#",
"properties": {
"principal": {
"type": "ActiveDirectory",
"identity": {
"objectId": "#{laPrincipal}#",
"tenantId": "#{var_tenant}#"
}
}
}
}






Tokens (#{...}#) are replaced at pipeline runtime before the file is used in the PUT call.






Step 3 — PUT the Policy on the Relevant Connections



Two approaches exist depending on how connections are named in the resource group.






Approach A — All connections



Assign the policy to every Microsoft.Web/connections in the resource group. This works well when all connections in the resource group belong to a single Logic App's domain:




$connections = az resource list `
-g $resourceGroup `
--resource-type Microsoft.Web/connections | ConvertFrom-Json

$maxAttempts = 10
$delaySeconds = 15

foreach ($conn in $connections) {
$url = "https://management.azure.com/subscriptions/$subscriptionId" +
"/resourceGroups/$resourceGroup" +
"/providers/Microsoft.Web/connections/$($conn.name)" +
"/accessPolicies/policy-$logicAppName" +
"?api-version=2018-07-01-preview"

# Idempotency: skip if the correct policy already exists
$existingPrincipal = az rest --method get --url $url `
--query "properties.principal.identity.objectId" --output tsv 2>&1
if ($existingPrincipal -eq $laPrincipal) {
Write-Host "Policy already correct on $($conn.name), skipping."
continue
}

# ARM returns HTTP 500 if the same principal already has a policy under a different name.
# Delete any stale policy for this principal before creating the new one.
$staleIds = az rest --method get `
--url "https://management.azure.com/subscriptions/$subscriptionId/resourceGroups/$resourceGroup/providers/Microsoft.Web/connections/$($conn.name)/accessPolicies?api-version=2018-07-01-preview" `
--query "value[?properties.principal.identity.objectId=='$laPrincipal'].id" `
--output tsv 2>&1

$deleted = $false
foreach ($policyId in ($staleIds -split "`n" | Where-Object { $_ -ne '' })) {
az rest --method delete `
--url "https://management.azure.com${policyId}?api-version=2018-07-01-preview" 2>&1 | Out-Null
Write-Host "Deleted stale policy on $($conn.name): $policyId"
$deleted = $true
}
if ($deleted) { Start-Sleep -Seconds 2 } # Allow ARM to process deletion

# PUT the new policy with retry
$success = $false
for ($i = 1; $i -le $maxAttempts; $i++) {
$result = az rest --method put --url $url --body @accessPolicyProperties.json 2>&1
if ($LASTEXITCODE -eq 0) { $success = $true; break }
Write-Host "Attempt $i/$maxAttempts for $($conn.name) failed: $result"
if ($i -lt $maxAttempts) { Start-Sleep -Seconds $delaySeconds }
}

if (-not $success) {
Write-Error "Failed to set access policy on $($conn.name) after $maxAttempts attempts."
exit 1
}
}









Approach B — Prefix filtering + explicit shared connections



When the resource group contains connections for multiple Logic Apps — named with a prefix convention such as lawf-io-*, lawf-order-* — you can filter the connection list by prefix. This avoids assigning access policies across unrelated connections.




# Only the connections owned by this Logic App
$connections = az resource list `
-g $resourceGroup `
--resource-type Microsoft.Web/connections `
--query "[?starts_with(name, 'lawf-io-')]" | ConvertFrom-Json

foreach ($conn in $connections) {
# ... same PUT logic as Approach A
}






Some connections may be shared across Logic Apps and not carry any particular prefix (e.g. office365-conn, onpremfs-conn). These are passed in as an explicit parameter — either from the pipeline call or from a variable group — and processed separately:




# sharedConnections: space or comma-separated list from pipeline parameter or variable group
$sharedParam = $sharedConnectionsParam.Trim()
if ([string]::IsNullOrWhiteSpace($sharedParam) -or $sharedParam -eq 'none') {
$sharedParam = $env:VAR_SHARED_CONNECTIONS.Trim()
}

foreach ($connName in ($sharedParam -split '[,\s]+' | Where-Object { $_ -ne '' })) {
$exists = az resource list `
-g $resourceGroup `
--resource-type Microsoft.Web/connections `
--query "[?name=='$connName'].name" -o tsv 2>&1

if (-not $exists) {
Write-Host "Shared connection '$connName' not found in $resourceGroup, skipping."
continue
}

$url = "https://management.azure.com/subscriptions/$subscriptionId" +
"/resourceGroups/$resourceGroup" +
"/providers/Microsoft.Web/connections/$connName" +
"/accessPolicies/policy-$logicAppName" +
"?api-version=2018-07-01-preview"

az rest --method put --url $url --body @accessPolicyProperties.json
}






This approach keeps the default access surface minimal — only named shared connections are granted, not everything in the resource group.









Generating connections.json



Logic Apps Standard requires a connections.json file alongside the workflow definitions. This file maps connection reference names (used in workflow.json) to the actual ARM resource IDs and live runtime URLs of the managed connections.



Rather than maintaining this file manually — which would require hard-coded subscription IDs and environment-specific runtime URLs — the pipeline generates it dynamically from Azure at deploy time using a script.






What the script does



The script queries the resource group for:




  1. Managed API connections (Microsoft.Web/connections) — reads each connection's ARM properties to get its api.id, resource id, and connectionRuntimeUrl. The connectionRuntimeUrl is the endpoint Logic Apps Standard uses at runtime to route calls through the managed connector; it is only available from ARM after the connection is provisioned.


  2. Function app connections (Microsoft.Web/sites with HTTP-triggered functions, when -withFunctions is passed) — reads each function app, lists its HTTP-triggered functions, and fetches the host key used for authentication.





Function Get-ApiConnections {
$apiConnections = @{}

$resources = Get-AzResource -ResourceGroupName $resourceGroup -ResourceType Microsoft.Web/connections

# Optional: filter by name prefix when multiple Logic Apps share the same resource group
if ($connectionNamePrefix) {
$resources = $resources | Where-Object { $_.Name.StartsWith($connectionNamePrefix) }
}

$resources | ForEach-Object {
$name = $_.Name
$connectionResource = Get-AzResource -ResourceId $_.Id

$apiConnections[$name] = @{
"api" = @{ "id" = $connectionResource.Properties.api.id }
"connection" = @{ "id" = $_.Id.ToLower() }
"connectionRuntimeUrl" = $connectionResource.Properties.connectionRuntimeUrl
"authentication" = @{ "type" = "ManagedServiceIdentity" }
}
}

return $apiConnections
}

Function Get-FunctionConnections {
$functionConnections = @{}

$sites = Get-AzResource -ResourceGroupName $resourceGroup -ResourceType Microsoft.Web/sites
foreach ($site in $sites) {
# Enumerate HTTP-triggered functions via ARM (no func CLI required)
$funcList = Invoke-AzRestMethod `
-Path "$($site.Id)/functions?api-version=2022-03-01" -Method GET
if ($funcList.StatusCode -ne 200) { continue }

$functions = ($funcList.Content | ConvertFrom-Json).value |
Where-Object { $_.properties.invoke_url_template }
if (-not $functions) { continue }

# Get the host key for authentication
$keysResp = Invoke-AzRestMethod `
-Path "$($site.Id)/host/default/listkeys?api-version=2022-03-01" -Method POST -Payload '{}'
$keysObj = $keysResp.Content | ConvertFrom-Json
$hostKey = $keysObj.functionKeys.default ?? $keysObj.masterKey

foreach ($func in $functions) {
$funcName = $func.id.Split('/')[-1]
$functionConnections[$funcName] = @{
"function" = @{ "id" = $func.id }
"triggerUrl" = $func.properties.invoke_url_template
"authentication" = @{ "type" = "QueryString"; "name" = "Code"; "value" = $hostKey }
"displayName" = $funcName
}
}
}

return $functionConnections
}









Output structure



The script writes a compressed single-line JSON that the pipeline then uses directly:




{
"managedApiConnections": {
"office365-conn": {
"api": { "id": "/subscriptions/.../managedApis/office365" },
"connection": { "id": "/subscriptions/.../connections/office365-conn" },
"connectionRuntimeUrl": "https://xxxxx.common.logic-westeurope.azure-apihub.net/apim/office365/yyy",
"authentication": { "type": "ManagedServiceIdentity" }
},
"sftp-partner-conn": {
"api": { "id": "/subscriptions/.../managedApis/sftpwithssh" },
"connection": { "id": "/subscriptions/.../connections/sftp-partner-conn" },
"connectionRuntimeUrl": "https://xxxxx.common.logic-westeurope.azure-apihub.net/apim/sftpwithssh/zzz",
"authentication": { "type": "ManagedServiceIdentity" }
}
},
"functionConnections": {
"ParseDocument": {
"function": { "id": "/subscriptions/.../functions/ParseDocument" },
"triggerUrl": "https://my-fa.azurewebsites.net/api/parsedocument",
"authentication": { "type": "QueryString", "name": "Code", "value": "<host-key>" },
"displayName": "ParseDocument"
}
}
}









Calling the script in the pipeline



Simple (all connections in the RG):




.\Generate-Connections.ps1 `
-resourceGroup $resourceGroup `
-outputLocation connections.json `
-withFunctions






With prefix filtering and shared connections (Approach B):




.\Generate-Connections.ps1 `
-resourceGroup $resourceGroup `
-outputLocation connections.json `
-connectionNamePrefix "lawf-io-" `
-withFunctions
# Shared connections not matching the prefix are handled separately
# by passing -sharedConnectionNames to a modified version of the script,
# or by running the script a second time against a second resource group.







Key constraint: The key in managedApiConnections is the ARM resource name of the connection. This must exactly match the referenceName used in workflow.json. If the resource is named office365-conn but the workflow references office365, the runtime connection lookup will fail silently.







Injecting service provider connections



Managed API connections are generated from Azure. Service provider connections (built-in) are not ARM resources and cannot be queried — they are injected into the generated file by the pipeline via sed before it is deployed:




# Inject serviceProviderConnections block into the generated connections.json
sed -i 's/"functionConnections"/"serviceProviderConnections": {
"spc-servicebus": {
"displayName": "spc-servicebus",
"parameterValues": {
"connectionString": "@appsetting('
\''serviceBusConnection'\'')"
},
"serviceProvider": { "id": "\/serviceProviders\/serviceBus" }
}
},"functionConnections"/g'
connections.json






The final connections.json therefore contains all three sections — managed API connections (from Azure), service provider connections (injected), and function connections (from Azure) — assembled by the pipeline before deploy.









Shared vs. Logic-App-Specific Connections



The pattern supports both types transparently:























Connection type Provisioning Access policy
Shared (e.g. Office 365, on-prem gateway) One Bicep deployment for the whole platform Each Logic App gets its own named policy
Logic-App-specific (e.g. a partner SFTP only one LA uses) Still provisioned in the same shared Bicep step Only the owning Logic App gets a policy


The access policy layer is what enforces the distinction at runtime — there is no need to use separate resource groups or separate ARM deployments.









Service Provider Connections: No Access Policies Needed



Built-in service provider connections (Service Bus, Azure Blob, FTP, etc.) do not create an ARM resource — they are configured entirely inside connections.json using app setting references:




"serviceProviderConnections": {
"spc-servicebus": {
"parameterValues": {
"connectionString": "@appsetting('serviceBusConnection')"
},
"serviceProvider": { "id": "/serviceProviders/serviceBus" }
}
}






The actual connection string is stored as a Logic App app setting, injected by the pipeline at deploy time. No access policy is needed because authentication is handled by the connection string itself — the Logic App owns it directly.



The trade-off: managed API connections give you identity-based, policy-controlled access at the ARM level. Service provider connections are simpler to set up but rely on secrets stored in app settings.









End-to-End Pipeline Flow






1. provisionApps  →  Bicep deploys Logic App + all shared connections (Incremental)
2. provisionConns → Bicep deploys any missing managed API connections
3. fetchIdentity → Poll for Logic App managed identity principal ID
4. injectTokens → Replace #{laPrincipal}# and #{var_tenant}# in access policy template
5. assignPolicies → PUT access policy for this Logic App on each relevant connection
(idempotent: skip if correct, delete stale, retry on failure)
6. mergeAppSettings → az webapp config appsettings set (connection strings, keys)
7. generateConns → Generate-Connections.ps1 queries ARM → writes connections.json
8. injectSPCs → sed injects serviceProviderConnections into connections.json
9. zipDeploy → ArchiveFiles + AzureFunctionApp@1 deploys workflow zip to Logic App












Summary








































Concern How it is addressed
Shared connection infrastructure Single Bicep deployment, --mode Incremental, one resource per external system
Logic App isolation ARM access policy per Logic App identity on each connection
Precise access scoping Connection prefix filter + explicit shared connection list (Approach B)
Stale policy conflicts Pipeline detects and removes stale policies before re-assigning, with retries
Dynamic runtime URLs
connections.json generated from ARM at deploy time — never committed
Service provider connections App settings injection + sed into generated connections.json
Function connections Discovered from ARM, host key fetched and embedded automatically


The result is a platform where connections are provisioned once, runtime URLs are always current, and each Logic App can only authenticate to the connections it is explicitly authorised to use — enforced at the Azure control plane, not in application code.

Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten Securing Shared API Connections in Azure Logic Apps Standard with Access Policies

Thematisch verwandte Begriffe: Securing, Shared, Connections, Azure · 6 Treffer

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Zum Aktualisieren ziehen
ZERO-DAY CVE-2026-94040 | A flaw has been found in vas3k TaxHacker up to 0.8.5. Affected by this v…
Advisory →
TTS Reader • tsecurity.de Voice
tsecurity.de Icon
tsecurity.de App
Offline-Lesen, Eilmeldungen & 0ms Ladezeit

Installiere tsecurity.de direkt auf deinen Home-Bildschirm für das ultimative Vollbild-Magazinerlebnis ohne Browser-Leisten.

Nächster Beitrag
Themen-Radar & Intelligence Matrix
Echtzeit-Taxonomie nach Angriffsvektoren & Plattformen

tsecurity.de Live Threat Radar

🔴 LIVE RADAR
MONITORING
AKTIV
CVE-DATENBANK
LIVE
🔍
Community Radar & Live Chat
Sentinel Bot online • Live-Stream
Dein Cluster: Security Explorer
Match:
lädt…
Verbindung zum Community-Stream wird aufgebaut...
Bearbeitungsmodus — Senden überschreibt deine Nachricht
Community-Puls — was gerade passiert
lädt…
Aktivitäten deiner Analysten
lädt…
Neues Thema oder Eilmeldung einreichen

Reiche interessante Links, Zero-Days oder Debatten ein. Die Community entscheidet per Upvote über die Veröffentlichung.

Heiß diskutierte Einreichungen
🔖 Gespeicherte Artikel
📂 Keine gespeicherten Artikel vorhanden.
Zurück Ziehen Vor
Links: vorheriger Artikel Rechts: nächster Artikel unten: schließen
News NIS-2 Frühwarnung Tier-1 Intel ⏱️ 3 Min vor 10 Min
Artikeldaten werden geladen...

Zurück: vorheriger Vor: nächster
↗ Original-Quelle
Social Reaktionen Deine Reaktion zählt
Einstufung & Relevanz-Poll 0 Stimmen
In sozialen Netzwerken teilen 1-Klick