saif-resources / servicebus-queue¶
⚠️ Experimental: This module is experimental and still evolving — inputs, outputs, and behavior may change without a deprecation period. Pin your module version carefully and expect rough edges before relying on it in production.
App-scoped Azure Service Bus queue module. Provisions one or more queues on the shared, per-environment/per-domain Service Bus namespace for an app's own internal producer/consumer messaging, with least-privilege RBAC scoped to each queue itself.
Usage¶
module "servicebus_queue" {
source = "app.terraform.io/SAIFCorp/resources/saif//modules/servicebus-queue"
version = "~> 3.9.0"
context = module.environment.context
identity = module.identity.identity
# A single, default queue is provisioned automatically if `queues` is left unset.
# To provision multiple independent queues, key the map by a stable name:
queues = {
intake = {
max_delivery_count = 10
}
retries = {
max_delivery_count = 20
dead_lettering_on_message_expiration = true
}
}
}
# Compose app_settings
module "webapp" {
app_settings = merge(
module.servicebus_queue.app_settings,
# ...
)
}
Inputs¶
| Name | Description | Required |
|---|---|---|
context |
Platform context from environment module |
yes |
identity |
Identity bundle from identity module |
yes |
queues |
Map of queue definitions keyed by a stable identity name (default: a single default queue) — see below |
no |
Each entry in queues supports:
| Name | Description | Default |
|---|---|---|
max_delivery_count |
Poison-message threshold before dead-lettering | 10 |
lock_duration |
ISO 8601 message lock duration | PT30S |
default_message_ttl |
ISO 8601 default message TTL | P14D |
dead_lettering_on_message_expiration |
Dead-letter expired messages instead of discarding them | true |
requires_duplicate_detection |
Enable duplicate detection | false |
duplicate_detection_history_time_window |
ISO 8601 duplicate-detection window | PT10M |
max_size_in_megabytes |
Maximum queue size | 1024 |
dlq_reader_identities |
Map of identity keys to Entra ID object IDs granted DLQ read access on this queue | {} |
dlq_manager_identities |
Map of identity keys to Entra ID object IDs granted DLQ manage access on this queue | {} |
Outputs¶
| Name | Description |
|---|---|
queue_names |
Map of queue key to Service Bus queue name |
queue_ids |
Map of queue key to Service Bus queue resource ID |
namespace_name |
The Service Bus namespace name the queues were created on |
app_settings |
One ServiceBusQueueName__{key} entry per queue in queues |
What it creates¶
- One Service Bus queue per entry in
queueson the shared namespace (DLQ, retry, and duplicate-detection independently configurable per queue) - RBAC: Azure Service Bus Data Sender + Azure Service Bus Data Receiver → the app's App Registration service principal, scoped to each queue only
- Optional RBAC: Azure Service Bus Data Receiver / Azure Service Bus Data Owner on a queue for its
dlq_reader_identities/dlq_manager_identities(ops/on-call access to poison messages)
RBAC notes¶
The app's App Registration service principal receives Sender and Receiver on every queue, scoped to that queue's own azurerm_servicebus_queue.this[key].id rather than the namespace — least privilege for queues that are private to a single app. Only the SP is granted access: the app runtime authenticates via EnvironmentCredential using the SP's client ID (see the identity module's AZURE_CLIENT_ID app setting), so the UAMI is never used as a data-plane credential here and is intentionally not granted queue access. This is intentionally distinct from the namespace-wide grant proposed for generated APIs publishing to shared topics (see #1144); that grant serves many operations across shared topics, while these queues are single-app resources where the tighter, per-queue scope is both possible and preferable.
Azure RBAC has no separate scope for a queue's dead-letter sub-queue — granting Receiver/Owner for DLQ access also permits receiving/managing live messages on the queue itself. dlq_reader_identities/dlq_manager_identities take Entra ID object IDs directly (not group names resolved via a data "azuread_group" lookup), because such lookups execute at plan time and fail the Organization Policy Check — the same convention as the storage module's contributor_group_id (see the Service Bus Queue development guide).
Role assignments are deduplicated by resolved principal ID, not by identity alias: Azure rejects a duplicate (principal_id, role, scope) role assignment, so two dlq_reader_identities/dlq_manager_identities aliases that resolve to the same object ID collapse to a single grant, and any dlq_reader_identities alias that duplicates the app's own SP is skipped entirely (it's already granted Receiver above).
Connection string¶
The namespace's fully-qualified host is published centrally to shared App Configuration as ConnectionStrings:messaging (see cloud-foundations#134) — apps bind with AddAzureServiceBusClient(connectionName: "messaging") and do not need a per-app connection string, so this module's app_settings output only carries the queue name(s). messaging is the universal connection name used across all consumers of this module, so it is not configurable.
The module deliberately emits no connection entry of its own. AddAzureDefaults() loads App Configuration after the App Service environment-variable provider, and the last configuration provider wins, so a ConnectionStrings__messaging app setting could not override the centrally published value anyway.
Providers¶
| Name | Version |
|---|---|
| azurerm | >= 4.0, < 5.0 |
Inputs¶
| Name | Description | Type | Default | Required |
|---|---|---|---|---|
| context | Platform context from the environment module | any |
n/a | yes |
| identity | Identity bundle from the identity module. The app's App Registration service principal is granted Azure Service Bus Data Sender and Data Receiver on every queue in var.queues, scoped to each queue only — the UAMI is not granted access, since the app runtime authenticates as the SP via EnvironmentCredential. | any |
n/a | yes |
| queues | Map of queue definitions, keyed by a stable identity name (e.g. "intake", "retries"). Each queue is provisioned independently on the shared namespace, with its own DLQ/poison-message configuration and RBAC. Entries default to a single "default" queue when unset. |
map(object({ |
{ |
no |
Outputs¶
| Name | Description |
|---|---|
| app_settings | App settings map for web app configuration — one ServiceBusQueueName__{key} entry per queue in var.queues. No connection entry is emitted: the namespace's fully-qualified host is published centrally to shared App Configuration as ConnectionStrings:messaging (see cloud-foundations#134) and read via AddAzureServiceBusClient(connectionName: "messaging"). App Configuration is loaded after the App Service environment-variable provider and therefore wins, so a connection entry emitted here could not override the central value anyway. |
| namespace_name | The Service Bus namespace name the queues were created on |
| queue_ids | Map of queue key to Service Bus queue resource ID |
| queue_names | Map of queue key to Service Bus queue name |
Resources¶
- resource.azurerm_role_assignment.dlq_manager (/terraform-docs/modules/servicebus-queue/main.tf#56)
- resource.azurerm_role_assignment.dlq_reader (/terraform-docs/modules/servicebus-queue/main.tf#49)
- resource.azurerm_role_assignment.receiver (/terraform-docs/modules/servicebus-queue/main.tf#41)
- resource.azurerm_role_assignment.sender (/terraform-docs/modules/servicebus-queue/main.tf#34)
- resource.azurerm_servicebus_queue.this (/terraform-docs/modules/servicebus-queue/main.tf#18)