This article explains how to bridge an existing RabbitMQ broker to the Warewolf Execution Engine without exposing RabbitMQ to Azure or running a queue-worker process per trigger. It’s the right fit when a customer already has RabbitMQ producing messages on-prem and wants those messages to execute a workflow on the engine, with no code change on the producer side.
Download the Deployment Scripts. Download the Warewolf Azure Execution Engine deployment scripts from warewolf.io/release-notes.php and extract the
.zipso that the scripts land inD:\ExecutionEngine\Scripts. All examples in this article assume that path. This article assumes the engine itself is already deployed — see Deploy to Azure Functions if it isn’t yet.
Why a bridge, and not a queue-worker container per trigger
RabbitMQ speaks AMQP 0.9.1; Azure Service Bus speaks AMQP 1.0 (or its own protocol). They can’t talk to each other directly. The shovel bridge uses two first-class, independently-provisioned pieces to connect them:
- RabbitMQ’s own Shovel plugin — a dynamic, broker-managed forwarder that reads from an existing RabbitMQ queue and republishes each message, verbatim, onto a Service Bus queue over AMQP 1.0. No custom forwarding code, no extra process to keep alive.
- A Service Bus-triggered Function App (
Warewolf.Execution.ServiceBusWorker) — the compute step that actually calls the engine. Service Bus is a message broker; it cannot hold an OAuth token or make an outbound HTTP call, so this worker reads each message, authenticates via Managed Identity, and calls the engine’s/secureor/publicroute on its behalf.
┌──────────────┐ publish ┌─────────────────┐ Shovel ┌──────────────────┐ trigger ┌───────────────────┐
│ RabbitMQ │ ─────────▶ │ RabbitMQ │ ──────────▶ │ Azure Service │ ─────────▶ │ ServiceBusWorker │
│ producer │ │ source queue │ (AMQP │ Bus queue │ (message) │ Function App │
└──────────────┘ └─────────────────┘ 0.9.1→1.0) └──────────────────┘ └────────┬──────────┘
│ Managed Identity
▼
Execution Engine
/secure or /public
Message contract
Shovel forwards message bytes verbatim — it cannot reshape payloads. The RabbitMQ producer must publish exactly the JSON the worker expects:
{ "route": "secure", "workflow": "Hello World", "inputs": { "Name": "FromRabbitMq" } }
route(optional) —secure(default) orpublic.workflow(required) — the workflow name.inputs(optional) — a string map sent as query-string parameters.
If an existing RabbitMQ producer uses a different schema, it must be changed to emit this contract, or an adapter service must sit between the producer and the source queue — Shovel itself cannot transform messages.
Prerequisites
| Requirement | Notes |
|---|---|
| PowerShell 7.0+, Azure CLI 2.55.0+ | Same as every other script in the deployment scripts zip. |
| RabbitMQ Shovel plugins enabled on the broker | One-time, broker-host admin action: rabbitmq-plugins enable rabbitmq_shovel rabbitmq_shovel_management. Configure-RabbitMqShovel.ps1 probes for this and tells you if it’s missing — it cannot enable it remotely. |
| A broker-side TLS hostname-check fix | Required before any shovel can connect to a real Service Bus namespace — see the callout below. Also broker-host admin, also not automatable remotely. |
| An Entra “client apps” app role on the engine | The worker authenticates as a daemon — see How to configure Client Apps with Azure Entra ID and Easy Auth. Warewolf_ClientApps ships in the example auth config. |
Required broker fix — TLS hostname check. Azure Service Bus presents a wildcard certificate (
*.servicebus.windows.net). Erlang’s default certificate-hostname check does a literal match, not a wildcard-aware one, so without this fix every shovel connection to a real Service Bus namespace fails with{tls_alert,{bad_certificate,{bad_cert,{hostname_check_failed, ...}}}}in the broker’s own log (not visible via the Management API). Add this to the broker’sadvanced.config(%APPDATA%\RabbitMQ\advanced.configon Windows,/etc/rabbitmq/advanced.configon Linux) and restart the broker:[ {amqp10_client, [ {ssl_options, [ {customize_hostname_check, [ {match_fun, public_key:pkix_verify_hostname_match_fun(https)} ]} ]} ]} ].This keeps full TLS peer/chain validation enabled — it only fixes the hostname match, it doesn’t weaken certificate checking. On Erlang/OTP 26+ brokers you’ll also need to pass a CA bundle on the shovel’s destination URI (
-DestUriCaCertFileonConfigure-RabbitMqShovel.ps1, step 3 below) or the shovel crash-loops with{cacerts, undefined}— OTP 26 stopped falling back to an implicit trust store.
Step 1 — Deploy the Service Bus worker
Publish first — the script does not build, and its publish output must be a different folder from the engine’s own:
dotnet publish Dev/Warewolf.Execution.ServiceBusWorker/Warewolf.Execution.ServiceBusWorker.csproj -c Release -o D:\ServiceBusWorker\Publish
.\Deploy-WwExecutionServiceBusWorker.ps1 `
-ResourceGroup $ResourceGroup -Location $Location `
-StorageAccount stwwsbworker -AppName $ServiceBusWorkerApp `
-PublishPath D:\ServiceBusWorker\Publish `
-ServiceBusNamespace $ServiceBusNamespace -ServiceBusQueueName wwexecution-queue `
-CreateShovelSendRule $true `
-WwExecutionBaseUrl $EngineUrl -WwExecutionTenantId $TenantId `
-WwExecutionResourceAppId $ResourceAppId
This provisions, in order: the Service Bus namespace and destination queue (dead-lettering, configurable max delivery count), the worker’s Function App with a system-assigned Managed Identity for its listen connection (no SAS secret in its own app settings), and a queue-scoped Send-only SAS authorization rule (default name shovel-send) — the least-privilege credential the RabbitMQ Shovel plugin will use as its destination. Like every other script here, it’s params-first, prompt-if-missing, supports -DryRun, and writes a masked summary + transcript.
Configurable trigger binding
-ServiceBusQueueName (default wwexecution-queue) isn’t just a provisioning detail — it’s applied as an app setting the worker’s trigger actually binds to, so overriding it retargets the trigger, not only the queue that gets created. Four more parameters tune the trigger’s binding, which otherwise defaults to the worker’s committed values:
| Parameter | Default | What it controls |
|---|---|---|
-ServiceBusTriggerMaxConcurrentCalls |
16 | Max messages processed concurrently |
-ServiceBusTriggerPrefetchCount |
0 | Messages pre-fetched per replica ahead of the concurrency limit |
-ServiceBusTriggerMaxAutoLockRenewalMinutes |
5 | How long the worker keeps renewing a message’s peek-lock while processing it |
-ServiceBusTriggerAutoCompleteMessages |
true | Whether the host auto-completes a message on successful return |
Most deploys don’t need these — the defaults match production traffic patterns. They’re there for higher-throughput or slower-downstream scenarios. All five parameters above are only available on this standalone invocation; they aren’t forwarded when the worker is deployed as a companion of the engine (Deploy-WwExecutionEngine.ps1 -DeployServiceBusWorker) — that switch only carries shared subscription/tenant/resource-group/engine-URL context.
Step 2 — Authorize the worker’s Managed Identity
The worker is a daemon caller of the engine, same as any other app-only client — mirror the registration from How to configure Client Apps. -AppRolesToAssign fails loudly if Warewolf_ClientApps doesn’t already exist on the engine:
.\Configure-WwExecutionAuth-Clients.ps1 `
-ResourceAppId $ResourceAppId -TenantId $TenantId `
-ClientType Daemon -DaemonUseManagedIdentity `
-DaemonFunctionAppName $ServiceBusWorkerApp `
-DaemonFunctionAppResourceGroup $ResourceGroup `
-AppRolesToAssign Warewolf_ClientApps `
-NonInteractive
You’ll also need a matching secure.config WindowsGroupPermissions row granting Warewolf_ClientApps Execute=true on every workflow the bridge calls — same requirement as any other daemon caller, see Security — secure.config.
Step 3 — Configure the RabbitMQ shovel
This configures a dynamic shovel via the RabbitMQ Management HTTP API — nothing is written to the broker’s static config, and it’s safe to re-run. The destination SAS key is fetched live via az and never written to disk:
.\Configure-RabbitMqShovel.ps1 `
-RabbitMqManagementUri "https://:15671" `
-RabbitMqUsername -RabbitMqPassword `
-SourceQueue `
-ServiceBusNamespace $ServiceBusNamespace -ServiceBusQueueName wwexecution-queue `
-ServiceBusResourceGroup $ResourceGroup -ServiceBusSasKeyName shovel-send
If your broker is Erlang/OTP 26+, append -DestUriCaCertFile /etc/ssl/certs/ca-certificates.crt (or your platform’s CA bundle path) — see the TLS callout above. -DestUriVerifyNone exists as a last-resort escape hatch that disables all peer certificate validation; it’s a diagnostic tool, not a recommended setting.
Step 4 — Verify
# Confirm the queue exists and check its depth:
az servicebus queue show --namespace-name $ServiceBusNamespace --resource-group $ResourceGroup --name wwexecution-queue -o table
# Or schedule the standalone health monitor (Task Scheduler/cron/Azure Automation —
# RabbitMQ is customer/on-prem infra, not something a Function can reliably poll):
.\Monitor-RabbitMqShovel.ps1 -RabbitMqManagementUri "https://:15671" `
-RabbitMqUsername -RabbitMqPassword -ShovelName wwexecution-shovel
Publish a message to the RabbitMQ source queue and confirm it arrives on the Service Bus queue and is executed on the engine. A 500 from the engine means the worker’s Managed Identity lacks Warewolf_ClientApps, or secure.config has no matching Execute row for that workflow — engine authorization denials are wrapped as 500, not 403 (WOLF-8418), same as every other daemon caller.
The Management API reports a healthy, actively-forwarding shovel as either
runningorflow—flowjust means RabbitMQ’s own flow control is briefly throttling it (e.g. transient backpressure from the destination). It is not a failure state; don’t alert on it.
Security model
- The Shovel’s destination credential is a queue-scoped, Send-only SAS rule — it can never Listen or Manage.
- The worker’s own Service Bus listen connection uses Managed Identity by default — no SAS secret in the worker’s app settings at all.
- The worker calls the engine via Managed Identity too — see How to configure Client Apps and Security — secure.config.
- The destination Service Bus namespace must keep local (SAS) authentication enabled (
disableLocalAuth = false). RabbitMQ’s Shovel plugin only supports SASL PLAIN with a policy-name/key credential — it has no Entra/OAuth client, so it cannot use Managed Identity. If a security baseline flips this totrue, every shovel to that namespace stops connecting. This is independent of the worker’s own Managed Identity listen connection above — only the Shovel’s send-side credential needs SAS. - Rotate the
shovel-sendSAS key per your normal key-rotation schedule; rotating it requires re-runningConfigure-RabbitMqShovel.ps1so the Shovel picks up the new key.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
Shovel terminated, reason "failed to connect to destination" |
Either the TLS hostname-check fix isn’t applied, or disableLocalAuth has drifted to true on the namespace — both produce this identical message. |
Check the broker’s own log for a hostname_check_failed TLS alert (apply the advanced.config fix above) vs. run az servicebus namespace show --query disableLocalAuth and restore false if it drifted. |
Shovel stuck at "starting", never reaches running/flow |
OTP 26+ broker missing a CA bundle on the destination URI — crash-loops with {cacerts, undefined} in the broker log, too fast for the Management API to catch between retries. |
Re-run Configure-RabbitMqShovel.ps1 with -DestUriCaCertFile pointing at your platform’s CA bundle. |
500 from the engine after a message arrives |
Worker’s Managed Identity lacks Warewolf_ClientApps, or secure.config has no Execute=true row for the workflow. |
Re-check Step 2’s role assignment and the matching secure.config row. |
rabbitmq-plugins enable instructions on every Configure-RabbitMqShovel.ps1 run |
rabbitmq_shovel/rabbitmq_shovel_management aren’t enabled on the broker. |
Run the exact rabbitmq-plugins enable command the script prints, once, on the broker host. |
Teardown
The Service Bus worker has no dedicated rollback companion — its run summary JSON records what to remove manually (Function App, storage account, Service Bus namespace/queue, SAS rules). The shovel itself can be removed via the RabbitMQ Management UI or API (DELETE /api/parameters/shovel/{vhost}/{name}) — deleting it does not touch the Service Bus queue or the worker.
See also
Scripts/Deploy-WwExecutionServiceBusWorker.ps1— provisions the Azure side.Scripts/Configure-RabbitMqShovel.ps1— configures the RabbitMQ side.Scripts/Monitor-RabbitMqShovel.ps1— standalone health monitor for scheduled polling.docs/ShovelBridge-Architecture.md— full internal architecture reference, including every root-caused failure mode this article’s troubleshooting table summarizes.docs/Deploy-EndToEnd-Runbook.md§8.5 — the full copy-paste runbook this article is drawn from.- How to configure Client Apps with Azure Entra ID and Easy Auth — the daemon registration Step 2 depends on, and the
AzureServiceBusreference client this worker is built from. - Security — secure.config — the permission model the worker’s role resolves against.
- Deploy to Azure Functions — deploying the engine itself, if it isn’t already up.




