Skip to content

Latest commit

Β 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

☁️ Azure IoT Hub Service Bus Queue Routing Endpoint Terraform Module

A single IoT Hub custom routing endpoint that forwards device messages to a Service Bus queue. Secure by default: the empty call authenticates with a managed identity (identityBased), so no shared secret is required unless the caller explicitly opts into a connection string. Targets hashicorp/azurerm ~> 4.0.


Terraform azurerm Module Version Type Resources


🧩 Overview

This module manages a single IoT Hub custom routing endpoint that delivers device messages to a Service Bus queue you already own.

  • πŸ”Œ Creates one azurerm_iothub_endpoint_servicebus_queue keyed to an existing IoT Hub.
  • πŸ” Authenticates with a managed identity by default (identityBased) β€” a shared connection string is an explicit opt-in.
  • 🎯 Points at the target queue by endpoint_uri + entity_path (identity auth) or an embedded connection_string (key auth).
  • ⏱️ Exposes per-operation timeouts; this resource does not support Azure tags.
  • πŸ“€ Emits the endpoint's resource id first, then its name, for downstream azurerm_iothub_route wiring.

πŸ’‘ Why it matters: IoT Hub message routing decouples device ingestion from downstream processing. A Service Bus queue endpoint gives you ordered, load-levelled, competing-consumer delivery of device telemetry β€” and with identityBased auth, the hub reaches the queue through a managed identity, so there is no connection string to store, rotate, or leak.


❀️ Support this project

If this module saves you time, a little support goes a long way:


πŸ—ΊοΈ Where this fits in the family

flowchart LR
  hub["terraform-azurerm-iothub"]
  me["terraform-azurerm-iothub-endpoint-servicebus-queue"]
  sbns["terraform-azurerm-servicebus-namespace"]
  sbq["azurerm_servicebus_queue owned by the namespace module"]
  uai["terraform-azurerm-user-assigned-identity"]
  route["terraform-azurerm-iothub-route"]
  sib_eh["terraform-azurerm-iothub-endpoint-eventhub"]
  sib_top["terraform-azurerm-iothub-endpoint-servicebus-topic"]
  sib_sc["terraform-azurerm-iothub-endpoint-storage-container"]
  sib_cdb["terraform-azurerm-iothub-endpoint-cosmosdb-account"]
  hub -->|"iothub_id"| me
  sbns -->|"endpoint_uri"| me
  sbq -->|"entity_path"| me
  uai -->|"identity_id"| me
  me -->|"endpoint_names"| route
  hub -.->|"sibling endpoint"| sib_eh
  hub -.->|"sibling endpoint"| sib_top
  hub -.->|"sibling endpoint"| sib_sc
  hub -.->|"sibling endpoint"| sib_cdb
  classDef me fill:#0078D4,stroke:#004578,color:#ffffff;
  classDef target fill:#004578,stroke:#002d4d,color:#ffffff;
  classDef ext fill:#f2f2f2,stroke:#c8c8c8,color:#111111;
  class me me;
  class hub target;
  class sbns,sbq,uai,route,sib_eh,sib_top,sib_sc,sib_cdb ext;
Loading

The IoT Hub is owned by a sibling module and referenced by iothub_id. This module attaches one Service Bus queue endpoint to that hub, targeting a queue owned by the Service Bus namespace/queue modules and (for identity auth) a user-assigned identity. A routing rule module then directs matching messages to this endpoint by name. The Event Hub, Service Bus topic, storage container, and Cosmos DB account endpoints are parallel siblings that attach to the same hub.


🧬 What this module builds

flowchart LR
  in_id["name / resource_group_name / iothub_id"]
  in_auth["authentication_type / identity_id / subscription_id"]
  in_tgt["connection_string / endpoint_uri / entity_path"]
  res["azurerm_iothub_endpoint_servicebus_queue.this"]
  out["id / name"]
  in_id -->|"input"| res
  in_auth -->|"input"| res
  in_tgt -->|"input"| res
  res -->|"output"| out
  classDef me fill:#0078D4,stroke:#004578,color:#ffffff;
  class res me;
Loading

Resource inventory

Resource Cardinality Role
azurerm_iothub_endpoint_servicebus_queue.this single (keystone) The Service Bus queue routing endpoint attached to the named IoT Hub.

βœ… Provider / Versions

Requirement Value
Terraform >= 1.12.0
hashicorp/azurerm ~> 4.0
Provider block None in this module β€” the caller configures provider "azurerm", its authentication, and the mandatory features {} block.

Schema notes that bite

  • πŸ”’ name, resource_group_name, and iothub_id are force-new β€” changing any of them destroys and recreates the endpoint.
  • πŸ”’ The endpoint name must be unique across the hub's endpoint types. The names events, operationsMonitoringEvents, fileNotifications, and $default are reserved and cannot be used.
  • πŸ”‘ keyBased auth requires connection_string; it is the only valid target spec for key auth.
  • πŸͺͺ identityBased auth requires endpoint_uri and entity_path; a connection_string is not used.
  • ⚠️ identity_id is only valid with identityBased and must reference one of the IoT Hub's assigned identities. Omit it to fall back to the hub's system-assigned identity.
  • ℹ️ subscription_id defaults to the parent IoT Hub's subscription when omitted.

πŸ”‘ Required Azure RBAC Roles / Permissions

Least-privilege at the parent IoT Hub scope:

  • Microsoft.Devices/IotHubs/write and Microsoft.Devices/IotHubs/read on the IoT Hub β€” Contributor scoped to the hub (there is no built-in "IoT Hub Contributor" role, and the four real IoT Hub roles are data-plane only, so none can create a routing endpoint) (or Contributor scoped to the hub) covers these.
  • For identityBased routing, the hub's identity needs Azure Service Bus Data Sender on the target queue so it can deliver messages.

Azure Prerequisites

  • An existing IoT Hub (azurerm_iothub) and a target Service Bus queue in a supported US Azure region.
  • For identityBased auth, a managed identity assigned to the IoT Hub with data-plane access to the queue (Azure Service Bus Data Sender).
  • The Microsoft.Devices resource provider registered on the subscription.

πŸ“ Module Structure

terraform-azurerm-iothub-endpoint-servicebus-queue/
β”œβ”€β”€ providers.tf     # required_version + azurerm ~> 4.0 pin; no provider block
β”œβ”€β”€ variables.tf     # typed inputs: name, resource_group_name, iothub_id, authentication_type, connection_string, endpoint_uri, entity_path, identity_id, subscription_id, timeouts
β”œβ”€β”€ main.tf          # keystone azurerm_iothub_endpoint_servicebus_queue.this + dynamic timeouts
β”œβ”€β”€ outputs.tf       # id (first), then name
β”œβ”€β”€ README.md        # this document
β”œβ”€β”€ SCOPE.md         # the cross-module contract
β”œβ”€β”€ LICENSE          # MIT
└── .gitignore       # canonical library ignore set

βš™οΈ Quick Start

provider "azurerm" {
  features {}
}

module "servicebus_queue_endpoint" {
  source = "git::https://github.com/microsoftexpert/terraform-azurerm-iothub-endpoint-servicebus-queue.git?ref=v1.0.0"

  name                = "telemetry-to-sbq"
  resource_group_name = "rg-iot-eastus"
  iothub_id           = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-iot-eastus/providers/Microsoft.Devices/IotHubs/iot-plant-eastus"

  # identityBased is the default β€” no connection string required.
  endpoint_uri = "sb://sb-iot-eastus.servicebus.windows.net"
  entity_path  = "device-telemetry"
}

ℹ️ The caller owns the provider "azurerm" block, its authentication (Azure CLI, managed identity, or OIDC), and the mandatory features {} block. This module declares no provider configuration.

πŸ”’ With no authentication_type set, the endpoint uses identityBased β€” the safe default. No shared secret is required.


πŸ”Œ Cross-Module Contract

Consumes

Input Type Source module
iothub_id string terraform-azurerm-iothub
endpoint_uri / entity_path string terraform-azurerm-servicebus-namespace β€” the namespace's endpoint, and the queue's own name from that module's queues map
identity_id string terraform-azurerm-user-assigned-identity
connection_string string (sensitive) provisioned out of band (key auth only)

Emits

Output Description Consumed by
id Resource ID of the routing endpoint (emitted first). terraform-azurerm-iothub-route
name Endpoint name. routing rules referencing the endpoint by name

πŸ“š Example Library

The examples below reference existing resources by ID or name rather than creating them; this module owns only its own resource. Those references are declared inputs:

variable "iot_identity_audit_id" {
  description = "id of an existing iot identity audit that these examples reference but do not create."
  type        = string
}

variable "iot_identity_ingest_id" {
  description = "id of an existing iot identity ingest that these examples reference but do not create."
  type        = string
}

variable "servicebus_queue_name" {
  description = "name of an existing servicebus queue that these examples reference but do not create."
  type        = string
}
1 Β· Minimal, secure default (identityBased)

The empty-ish call: identity auth is used because it is the default. The hub's system-assigned identity delivers to the queue.

module "sbq_endpoint" {
  source = "git::https://github.com/microsoftexpert/terraform-azurerm-iothub-endpoint-servicebus-queue.git?ref=v1.0.0"

  name                = "telemetry-to-sbq"
  resource_group_name = "rg-iot-eastus"
  iothub_id           = module.iothub.id

  endpoint_uri = "sb://sb-iot-eastus.servicebus.windows.net"
  entity_path  = "device-telemetry"
}

πŸ”’ authentication_type defaults to identityBased; no connection string is stored anywhere.

2 Β· Explicit identityBased for auditability

State the secure default explicitly so the posture is obvious in review.

module "sbq_endpoint" {
  source = "git::https://github.com/microsoftexpert/terraform-azurerm-iothub-endpoint-servicebus-queue.git?ref=v1.0.0"

  name                = "telemetry-to-sbq"
  resource_group_name = "rg-iot-eastus"
  iothub_id           = module.iothub.id

  authentication_type = "identityBased"
  endpoint_uri        = "sb://sb-iot-eastus.servicebus.windows.net"
  entity_path         = "device-telemetry"
}

πŸ’‘ Writing the value down makes the security choice visible even though it matches the default.

3 Β· User-assigned identity

Pin delivery to a specific user-assigned identity that is attached to the hub.

module "sbq_endpoint" {
  source = "git::https://github.com/microsoftexpert/terraform-azurerm-iothub-endpoint-servicebus-queue.git?ref=v1.0.0"

  name                = "telemetry-to-sbq"
  resource_group_name = "rg-iot-eastus"
  iothub_id           = module.iothub.id

  authentication_type = "identityBased"
  endpoint_uri        = "sb://sb-iot-eastus.servicebus.windows.net"
  entity_path         = "device-telemetry"
  identity_id         = module.iot_identity.id
}

⚠️ identity_id must be one of the IoT Hub's assigned identities. If it is not attached to the hub, the apply fails.

4 Β· keyBased with a connection string

Opt into key auth only when a connection string is genuinely unavoidable.

variable "sbq_connection_string" {
  type      = string
  sensitive = true
}

module "sbq_endpoint" {
  source = "git::https://github.com/microsoftexpert/terraform-azurerm-iothub-endpoint-servicebus-queue.git?ref=v1.0.0"

  name                = "telemetry-to-sbq"
  resource_group_name = "rg-iot-eastus"
  iothub_id           = module.iothub.id

  authentication_type = "keyBased"
  connection_string   = var.sbq_connection_string
}

πŸ”’ Provision the connection string out of band (for example, from a Key Vault reference) and pass it in β€” never commit it to source. Prefer identityBased where you can.

5 Β· Explicit target subscription

Route to a queue in a different subscription from the hub.

module "sbq_endpoint" {
  source = "git::https://github.com/microsoftexpert/terraform-azurerm-iothub-endpoint-servicebus-queue.git?ref=v1.0.0"

  name                = "telemetry-to-sbq"
  resource_group_name = "rg-iot-eastus"
  iothub_id           = module.iothub.id

  endpoint_uri    = "sb://sb-shared-eastus.servicebus.windows.net"
  entity_path     = "device-telemetry"
  subscription_id = "11111111-1111-1111-1111-111111111111"
}

ℹ️ subscription_id defaults to the hub's subscription; set it only when the target queue lives elsewhere.

6 Β· Custom timeouts

Override the provider's per-operation timeouts.

module "sbq_endpoint" {
  source = "git::https://github.com/microsoftexpert/terraform-azurerm-iothub-endpoint-servicebus-queue.git?ref=v1.0.0"

  name                = "telemetry-to-sbq"
  resource_group_name = "rg-iot-eastus"
  iothub_id           = module.iothub.id

  endpoint_uri = "sb://sb-iot-eastus.servicebus.windows.net"
  entity_path  = "device-telemetry"

  timeouts = {
    create = "30m"
    delete = "30m"
  }
}

ℹ️ Unset timeout fields fall back to provider defaults; leave timeouts as null to accept them entirely.

7 Β· Referencing upstream Service Bus outputs

Wire the namespace endpoint and queue name from sibling modules instead of hardcoding them.

module "sbq_endpoint" {
  source = "git::https://github.com/microsoftexpert/terraform-azurerm-iothub-endpoint-servicebus-queue.git?ref=v1.0.0"

  name                = "telemetry-to-sbq"
  resource_group_name = module.resource_group.name
  iothub_id           = module.iothub.id

  endpoint_uri = module.servicebus_namespace.endpoint
  entity_path  = var.servicebus_queue_name
}

πŸ’‘ Sourcing the URI and queue name from module outputs keeps a rename in one place and lets Terraform order creation correctly.

8 Β· Naming by environment

Derive the endpoint name from an environment value for repeatable multi-stage deployments.

variable "environment" {
  type    = string
  default = "prod"
}

module "sbq_endpoint" {
  source = "git::https://github.com/microsoftexpert/terraform-azurerm-iothub-endpoint-servicebus-queue.git?ref=v1.0.0"

  name                = "telemetry-to-sbq-${var.environment}"
  resource_group_name = "rg-iot-${var.environment}-eastus"
  iothub_id           = module.iothub.id

  endpoint_uri = "sb://sb-iot-${var.environment}-eastus.servicebus.windows.net"
  entity_path  = "device-telemetry"
}

πŸ”’ name is force-new β€” changing the environment value replaces the endpoint, which is the intended behavior across distinct environments.

9 Β· Multiple endpoints with for_each

Attach a keyed set of queue endpoints to one hub.

locals {
  endpoints = {
    telemetry = "device-telemetry"
    alerts    = "device-alerts"
    audit     = "device-audit"
  }
}

module "sbq_endpoint" {
  source   = "git::https://github.com/microsoftexpert/terraform-azurerm-iothub-endpoint-servicebus-queue.git?ref=v1.0.0"
  for_each = local.endpoints

  name                = "route-to-${each.key}"
  resource_group_name = "rg-iot-eastus"
  iothub_id           = module.iothub.id

  endpoint_uri = "sb://sb-iot-eastus.servicebus.windows.net"
  entity_path  = each.value
}

πŸ’‘ A stable map key (telemetry, alerts, audit) means adding or removing one endpoint never re-indexes the others.

10 Β· Per-endpoint queue and identity with for_each

Give each endpoint its own queue and user-assigned identity via a map of objects.

locals {
  endpoints = {
    telemetry = { queue = "device-telemetry", identity = var.iot_identity_ingest_id }
    audit     = { queue = "device-audit", identity = var.iot_identity_audit_id }
  }
}

module "sbq_endpoint" {
  source   = "git::https://github.com/microsoftexpert/terraform-azurerm-iothub-endpoint-servicebus-queue.git?ref=v1.0.0"
  for_each = local.endpoints

  name                = "route-to-${each.key}"
  resource_group_name = "rg-iot-eastus"
  iothub_id           = module.iothub.id

  authentication_type = "identityBased"
  endpoint_uri        = "sb://sb-iot-eastus.servicebus.windows.net"
  entity_path         = each.value.queue
  identity_id         = each.value.identity
}

ℹ️ Deeply-typed local maps keep a growing set of endpoints declarative and reviewable.

11 Β· Mixed auth across a fleet

Most endpoints use identity auth; a single legacy queue is opted into key auth deliberately.

locals {
  endpoints = {
    telemetry = { name = "route-to-telemetry", auth = "identityBased", uri = "sb://sb-iot-eastus.servicebus.windows.net", queue = "device-telemetry", conn = null }
    legacy    = { name = "route-to-legacy", auth = "keyBased", uri = null, queue = null, conn = var.legacy_connection_string }
  }
}

module "sbq_endpoint" {
  source   = "git::https://github.com/microsoftexpert/terraform-azurerm-iothub-endpoint-servicebus-queue.git?ref=v1.0.0"
  for_each = local.endpoints

  name                = each.value.name
  resource_group_name = "rg-iot-eastus"
  iothub_id           = module.iothub.id

  authentication_type = each.value.auth
  endpoint_uri        = each.value.uri
  entity_path         = each.value.queue
  connection_string   = each.value.conn
}

⚠️ The legacy entry uses a connection string β€” keep such opt-outs explicit and few, and review them each plan.

12 Β· Consuming the emitted id downstream

Feed the endpoint id into an output or another module.

module "sbq_endpoint" {
  source = "git::https://github.com/microsoftexpert/terraform-azurerm-iothub-endpoint-servicebus-queue.git?ref=v1.0.0"

  name                = "telemetry-to-sbq"
  resource_group_name = "rg-iot-eastus"
  iothub_id           = module.iothub.id

  endpoint_uri = "sb://sb-iot-eastus.servicebus.windows.net"
  entity_path  = "device-telemetry"
}

output "sbq_endpoint_id" {
  value = module.sbq_endpoint.id
}

πŸ’‘ The id is emitted first by convention and is the canonical cross-resource reference in Azure.

13 Β· Wiring the endpoint name into a route

A routing rule directs matching messages to this endpoint by name.

module "sbq_endpoint" {
  source = "git::https://github.com/microsoftexpert/terraform-azurerm-iothub-endpoint-servicebus-queue.git?ref=v1.0.0"

  name                = "telemetry-to-sbq"
  resource_group_name = "rg-iot-eastus"
  iothub_id           = module.iothub.id

  endpoint_uri = "sb://sb-iot-eastus.servicebus.windows.net"
  entity_path  = "device-telemetry"
}

module "telemetry_route" {
  source = "git::https://github.com/microsoftexpert/terraform-azurerm-iothub-route.git?ref=v1.0.0"

  name                = "telemetry-route"
  resource_group_name = "rg-iot-eastus"
  iothub_name         = module.iothub.name
  routing_source      = "DeviceMessages"
  condition           = "level = 'telemetry'"
  endpoint_names      = [module.sbq_endpoint.name]
  enabled             = true
}

πŸ’‘ A route references the endpoint by name, not id; the endpoint must exist before the route that targets it.

14 Β· πŸ—οΈ End-to-end composition

Wire the IoT Hub, a Service Bus namespace, and a user-assigned identity from sibling modules into this endpoint, then feed the endpoint into a route.

provider "azurerm" {
  features {}
}

module "resource_group" {
  source = "git::https://github.com/microsoftexpert/terraform-azurerm-resource-group.git?ref=v1.0.0"

  name     = "rg-iot-eastus"
  location = "eastus"
}

module "iot_identity" {
  source = "git::https://github.com/microsoftexpert/terraform-azurerm-user-assigned-identity.git?ref=v1.0.0"

  name                = "id-iot-ingest"
  resource_group_name = module.resource_group.name
  location            = module.resource_group.location
}

module "iothub" {
  source = "git::https://github.com/microsoftexpert/terraform-azurerm-iothub.git?ref=v1.0.0"

  name                = "iot-plant-eastus"
  resource_group_name = module.resource_group.name
  location            = module.resource_group.location
  sku                 = { name = "S1", capacity = 1 }
}

module "servicebus_namespace" {
  source = "git::https://github.com/microsoftexpert/terraform-azurerm-servicebus-namespace.git?ref=v1.0.0"

  name                = "sb-iot-eastus"
  resource_group_name = module.resource_group.name
  location            = module.resource_group.location

  # The queue is a child of the namespace module, not a module of its own.
  queues = {
    "device-telemetry" = { name = "device-telemetry" }
  }
}

module "sbq_endpoint" {
  source = "git::https://github.com/microsoftexpert/terraform-azurerm-iothub-endpoint-servicebus-queue.git?ref=v1.0.0"

  name                = "telemetry-to-sbq"
  resource_group_name = module.resource_group.name
  iothub_id           = module.iothub.id

  authentication_type = "identityBased"
  endpoint_uri        = module.servicebus_namespace.endpoint
  entity_path         = "device-telemetry"
  identity_id         = module.iot_identity.id
}

module "telemetry_route" {
  source = "git::https://github.com/microsoftexpert/terraform-azurerm-iothub-route.git?ref=v1.0.0"

  name                = "telemetry-route"
  resource_group_name = module.resource_group.name
  iothub_name         = module.iothub.name
  routing_source      = "DeviceMessages"
  condition           = "level = 'telemetry'"
  endpoint_names      = [module.sbq_endpoint.name]
  enabled             = true
}

output "sbq_endpoint_id" {
  value = module.sbq_endpoint.id
}

πŸ’‘ Attribute references let Terraform order creation automatically: resource group and identity, then the hub and Service Bus queue, then this endpoint, then the route β€” no depends_on required. The hub's identity must hold Azure Service Bus Data Sender on the queue for identityBased delivery to succeed.


πŸ“₯ Inputs

Identity (required)

Name Type Description
name string Name of the routing endpoint. Unique across the hub's endpoint types. Force-new.
resource_group_name string Resource group under which the endpoint is created. Force-new.
iothub_id string Resource ID of the parent IoT Hub. Force-new.

Configuration (optional)

Name Type Default Description
authentication_type string "identityBased" identityBased (managed identity) or keyBased (connection string). Secure default.
connection_string string (sensitive) null Service Bus queue connection string. Required and only valid with keyBased.
endpoint_uri string null Service Bus namespace endpoint URI. Required and only valid with identityBased.
entity_path string null Target queue name within the namespace. Required and only valid with identityBased.
identity_id string null User-assigned identity ID (must be attached to the hub). Only valid with identityBased.
subscription_id string null Target subscription. Defaults to the hub's subscription.
timeouts object(...) null Per-operation timeouts.
Full input schemas
variable "name" {
  type        = string
  description = <<-DESC
    The name of the routing endpoint. Must be unique across the IoT Hub's endpoint types.
    The names `events`, `operationsMonitoringEvents`, `fileNotifications`, and `$default` are
    reserved and cannot be used. Changing this forces a new resource to be created.
  DESC
}

variable "resource_group_name" {
  type        = string
  description = "The name of the resource group containing the **Service Bus queue** this endpoint targets -- NOT the IoT Hub's resource group. The hub is located entirely from `iothub_id`; this value is sent as part of the endpoint payload and never appears in the endpoint's Resource ID. Changing this forces a new resource to be created."
}

variable "iothub_id" {
  type        = string
  description = "The resource ID of the parent IoT Hub this endpoint attaches to. Changing this forces a new resource to be created."
}

variable "authentication_type" {
  type        = string
  default     = "identityBased"
  description = <<-DESC
    How the endpoint authenticates against the Service Bus namespace. One of `keyBased` (uses a
    `connection_string`) or `identityBased` (uses a managed identity plus `endpoint_uri` and
    `entity_path`). Defaults to `identityBased` so no shared secret is required; set to `keyBased`
    only when a connection string is unavoidable.
  DESC

  validation {
    condition     = contains(["keyBased", "identityBased"], var.authentication_type)
    error_message = "authentication_type must be one of: keyBased, identityBased."
  }
}

variable "connection_string" {
  type        = string
  default     = null
  sensitive   = true
  description = <<-DESC
    The connection string for the Service Bus queue. Mandatory and only valid when `authentication_type`
    is `keyBased`. Provision this out of band and pass a reference β€” never commit it to source.
  DESC
}

variable "endpoint_uri" {
  type        = string
  default     = null
  description = "URI of the Service Bus namespace endpoint. Mandatory and only valid when `authentication_type` is `identityBased`."
}

variable "entity_path" {
  type        = string
  default     = null
  description = "Name of the Service Bus queue within the namespace. Mandatory and only valid when `authentication_type` is `identityBased`."
}

variable "identity_id" {
  type        = string
  default     = null
  description = <<-DESC
    Resource ID of the user-assigned managed identity used to authenticate. Only valid when
    `authentication_type` is `identityBased`, and must be one of the IoT Hub's assigned identities.
    When omitted with `identityBased`, the IoT Hub's system-assigned identity is used.
  DESC
}

variable "subscription_id" {
  type        = string
  default     = null
  description = "The subscription ID for the endpoint. When omitted, it defaults to the subscription of the parent IoT Hub."
}

variable "timeouts" {
  type = object({
    create = optional(string)
    read   = optional(string)
    update = optional(string)
    delete = optional(string)
  })
  default     = null
  description = "Optional per-operation timeouts (create / read / update / delete)."
}

🧾 Outputs

Output Description Kind
id Azure Resource ID of the routing endpoint Passthrough
name Name of the routing endpoint, as created Passthrough
iothub_id Resource ID of the parent IoT Hub this endpoint is attached to Passthrough
iothub_name Name of the parent IoT Hub, taken from iothub_id Derived
iothub_resource_group_name Resource group holding the parent IoT Hub, taken from iothub_id Derived
iothub_subscription_id Subscription containing the parent IoT Hub, taken from iothub_id Derived
target_resource_group_name Resource group of the TARGET Service Bus namespace, read back from the endpoint record Passthrough
subscription_id Subscription recorded on the endpoint for the target namespace Passthrough
authentication_type The authentication mode in force, read back from Azure: keyBased or identityBased Passthrough
endpoint_uri Service Bus namespace URI in use on the identityBased path, read back from Azure Passthrough
entity_path Name of the Service Bus queue within the namespace on the identityBased path, read back from Azure Passthrough
identity_id User-assigned identity authenticating the endpoint, read back from Azure Passthrough
uses_managed_identity True when delivery authenticates with a managed identity and no stored credential exists for this endpoint Passthrough
uses_user_assigned_identity True when a specific user-assigned identity was named Derived
uses_system_assigned_identity True when the endpoint authenticates as the parent hub itself Derived
has_connection_string Whether a connection string was supplied Passthrough
key_based_target_host Namespace host extracted from the connection string on the keyBased path, null otherwise Derived
connection_string_is_never_emitted Constant true Constant
azure_returns_the_shared_key_masked Constant true Constant
rotating_only_the_shared_key_produces_no_plan_diff Constant true, and the sharpest edge in the keyBased path Constant
provider_default_authentication_type_is_key_based Constant true, recorded because this module deviates from it Constant
routes_reference_this_endpoint_by_name Constant true Constant
destroy_removes_only_the_endpoint_never_its_routes Constant true Constant
endpoint_name_is_unique_across_all_endpoint_types Constant true Constant
inline_iothub_endpoint_blocks_cannot_be_mixed_with_this_module Constant true Constant
every_change_rewrites_the_whole_parent_hub Constant true Constant
changes_to_sibling_endpoints_serialise_on_a_hub_lock Constant true Constant
replacement_deletes_before_it_recreates Constant true Constant
force_new_fields The inputs that force replacement rather than an in-place update Derived
send_permission_on_the_target_is_not_verifiable_here Constant true Constant
create_guards_the_queue_collection_by_testing_the_event_hub_collection Constant true, and unique to the two Service Bus endpoint types -- the Event Hub and Cosmos DB endpoints do not have it Constant
entity_path_may_end_in_a_period_or_slash Constant true, and the one behavioural difference from the Service Bus TOPIC endpoint sibling Constant

🧠 Architecture Notes

  • Single keystone, no children. This module owns exactly one resource, azurerm_iothub_endpoint_servicebus_queue.this. The IoT Hub, the Service Bus queue, and any routing rules are sibling concerns referenced by id or name, never created here.
  • Force-new fields. name, resource_group_name, and iothub_id all force replacement when changed. Because the endpoint name must also be unique across the hub's endpoint types, plan carefully before renaming β€” Terraform destroys and recreates, briefly interrupting delivery.
  • identityBased vs keyBased. The two auth modes take mutually exclusive target specs. identityBased (the default) uses endpoint_uri + entity_path and an optional identity_id; keyBased uses connection_string. Supplying the wrong combination for the selected mode is rejected at apply time.
  • connection_string is sensitive. The one secret-bearing input is marked sensitive = true. Provision it out of band and pass a reference; the module emits no secret in its outputs.
  • Identity attachment. When identity_id is set, it must reference one of the IoT Hub's assigned identities and that identity must hold Azure Service Bus Data Sender on the queue. Omitting it falls back to the hub's system-assigned identity.
  • features {} dependence. The provider "azurerm" block β€” including its mandatory features {} block β€” belongs to the caller's root module. If a plan fails to initialize in isolation, a missing caller-side features {} block is the usual cause.
  • No tags. The Service Bus queue endpoint resource does not support Azure tags, so the universal tail is timeouts only.

🧱 Design Principles

Concern Secure default (empty call) Opt-out (caller must type it)
Endpoint authentication authentication_type = "identityBased" (managed identity) set to "keyBased" with a connection_string
Secret handling no connection_string required or stored supply a connection_string (out of band, sensitive)
Secret emission no secret emitted from outputs β€”
Tags not supported by this resource; none accepted β€”
  • The enum that gates credential exposure defaults to the locked-down member (identityBased), enforced by a validation {} block listing the legal values.
  • The module never emits a secret; the one secret input (connection_string) is sensitive = true and must be provisioned out of band.

πŸš€ Runbook

# Initialize without a backend (plan-only, static analysis).
terraform init -backend=false

# Validate types and references.
terraform validate

# Confirm formatting.
terraform fmt -check
  • Pin the module with ?ref=v1.0.0 β€” never a branch.
  • No cloud apply is performed by this workflow; a human runs terraform apply from CI.

πŸ§ͺ Testing

The offline proof gate:

  • terraform init -backend=false β€” resolves the provider without contacting a backend.
  • terraform validate β€” proves every input type, the authentication_type enum, and each attribute reference is well-formed.
  • terraform fmt -check β€” confirms canonical formatting.

What only terraform plan (against a configured provider) exercises: whether the named IoT Hub and target Service Bus queue actually exist, whether the referenced identity_id is attached to the hub, and whether the hub's identity holds Azure Service Bus Data Sender on the queue. Validation cannot see live tenant state.


πŸ’¬ Example Output

$ terraform output
id   = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-iot-eastus/providers/Microsoft.Devices/IotHubs/iot-plant-eastus/endpoints/telemetry-to-sbq"
name = "telemetry-to-sbq"

πŸ” Troubleshooting

Symptom Cause Fix
IotHub not found / 404 on apply iothub_id does not match an existing hub. Confirm the hub exists in the target resource group and subscription; source iothub_id from the IoT Hub module's output.
endpoint name is reserved name is one of events, operationsMonitoringEvents, fileNotifications, or $default. Choose a unique, non-reserved endpoint name.
endpoint name already exists Another endpoint on the hub already uses this name. Endpoint names must be unique across the hub's endpoint types; pick a different name.
Messages not delivered with identityBased The hub's identity lacks data-plane access to the queue, or identity_id is not attached to the hub. Grant the identity Azure Service Bus Data Sender on the queue and ensure identity_id references an identity assigned to the hub.
connection_string is required / auth mismatch keyBased selected without a connection_string, or identity fields supplied with keyBased. Provide connection_string for keyBased; use endpoint_uri + entity_path for identityBased.
Plan shows the endpoint being replaced after a minor edit A force-new field (name, resource_group_name, or iothub_id) changed. Confirm the replacement is intended; if not, revert the field.
provider not initialized / features error The caller's root module is missing the provider "azurerm" { features {} } block. Add the provider block with features {} to the root module; this module intentionally omits it.

πŸ”— Related Docs


πŸ’™ "Infrastructure as Code should be standardized, consistent, and secure."