Skip to content

Latest commit

Β 

History

1 Commit

Folders and files

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

Repository files navigation

☁️ Azure IoT Hub Event Hub Routing Endpoint Terraform Module

A single IoT Hub custom routing endpoint that forwards device messages to an Event Hub. Secure by default: the empty call authenticates with a managed identity, so no shared secret is required. 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-to-cloud messages to an Event Hub you already own.

  • πŸ”Œ Creates one azurerm_iothub_endpoint_eventhub attached to an existing IoT Hub by iothub_id.
  • πŸ” Authenticates with a managed identity by default (identityBased) β€” no connection string, no shared secret.
  • πŸ”‘ Supports an explicit keyBased opt-in that carries a sensitive connection string when identity auth is not an option.
  • 🎯 Points at the target Event Hub by endpoint_uri + entity_path (identity auth) or embeds it in connection_string (key auth); it never creates the Event Hub or namespace.
  • πŸ“€ Emits the endpoint's resource id first, then its name, for downstream azurerm_iothub_route wiring.

πŸ’‘ Why it matters: Message routing lets an IoT Hub fan device telemetry out to purpose-built services without custom egress code. An Event Hub endpoint gives high-throughput streaming consumers a clean landing zone, and the identity-based default keeps the delivery path free of standing secrets.


❀️ 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-eventhub"]
  eh["terraform-azurerm-eventhub-namespace"]
  route["terraform-azurerm-iothub-route"]
  uai["terraform-azurerm-user-assigned-identity"]
  sbq["terraform-azurerm-iothub-endpoint-servicebus-queue"]
  sbt["terraform-azurerm-iothub-endpoint-servicebus-topic"]
  sc["terraform-azurerm-iothub-endpoint-storage-container"]
  cdb["terraform-azurerm-iothub-endpoint-cosmosdb-account"]
  hub -->|"iothub_id"| me
  eh -->|"endpoint_uri / entity_path"| me
  uai -->|"identity_id"| me
  me -->|"attaches to"| hub
  route -->|"routes to"| me
  hub -->|"iothub_id"| sbq
  hub -->|"iothub_id"| sbt
  hub -->|"iothub_id"| sc
  hub -->|"iothub_id"| 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 eh,route,uai,sbq,sbt,sc,cdb ext;
Loading

This module is one of five IoT Hub routing-endpoint siblings β€” Event Hub, Service Bus queue, Service Bus topic, storage container, and Cosmos DB account. Each attaches to the same parent IoT Hub by iothub_id. This endpoint targets an Event Hub namespace, optionally authenticates with a user-assigned identity, and is consumed by an azurerm_iothub_route that decides which messages reach it.


🧬 What this module builds

flowchart LR
  in_id["name / resource_group_name / iothub_id"]
  in_auth["authentication_type / identity_id"]
  in_idn["endpoint_uri / entity_path (identityBased)"]
  in_key["connection_string (keyBased)"]
  res["azurerm_iothub_endpoint_eventhub.this"]
  out["id / name"]
  in_id -->|"input"| res
  in_auth -->|"input"| res
  in_idn -->|"input"| res
  in_key -->|"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_eventhub.this single (keystone) The Event Hub routing endpoint registered on the parent 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.
  • πŸ”‘ keyBased auth requires connection_string; the URI and entity fields do not apply.
  • πŸ” identityBased auth requires endpoint_uri and entity_path; the connection string does not apply.
  • ⚠️ The endpoint name must be unique across the hub's endpoint types. events, operationsMonitoringEvents, fileNotifications, and $default are reserved and cannot be used.
  • ℹ️ identity_id is valid only with identityBased and must be one of the IoT Hub's assigned identities; omit it to fall back to the hub's system-assigned identity.

πŸ”‘ 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 this.
  • For identityBased routing, the hub's identity needs Azure Event Hubs Data Sender on the target Event Hub so it can deliver messages.

Azure Prerequisites

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

πŸ“ Module Structure

terraform-azurerm-iothub-endpoint-eventhub/
β”œβ”€β”€ 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_eventhub.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 "eventhub_endpoint" {
  source = "git::https://github.com/microsoftexpert/terraform-azurerm-iothub-endpoint-eventhub.git?ref=v1.0.0"

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

  # Secure default: identity-based auth, no shared secret.
  authentication_type = "identityBased"
  endpoint_uri        = "sb://ehns-telemetry-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.

πŸ”’ authentication_type defaults to identityBased, so the endpoint carries no secret. With no identity_id, delivery uses the IoT Hub's system-assigned identity.


πŸ”Œ Cross-Module Contract

Consumes

Input Type Source module
iothub_id string terraform-azurerm-iothub
endpoint_uri / entity_path string terraform-azurerm-eventhub-namespace β€” the sb:// URI composed from its name, and the event hub's own name from that module's event_hubs map. There is no separate event-hub module; an event hub is a for_each child of the namespace
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 "eventhub_name" {
  description = "name of an existing eventhub that these examples reference but do not create."
  type        = string
}

variable "key_vault_id" {
  description = "id of an existing key vault that these examples reference but do not create."
  type        = string
}
1 Β· Minimal, secure default (identityBased)

The empty-ish call: identity-based auth with the hub's system-assigned identity, no secret.

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

  name                = "telemetry-eventhub"
  resource_group_name = "rg-iot-eastus"
  iothub_id           = module.iothub.id
  endpoint_uri        = "sb://ehns-telemetry-eastus.servicebus.windows.net"
  entity_path         = "device-telemetry"
}

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

2 Β· Explicit identityBased for auditability

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

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

  name                = "telemetry-eventhub"
  resource_group_name = "rg-iot-eastus"
  iothub_id           = module.iothub.id
  authentication_type = "identityBased"
  endpoint_uri        = "sb://ehns-telemetry-eastus.servicebus.windows.net"
  entity_path         = "device-telemetry"
}

πŸ’‘ Writing authentication_type down documents the security choice even though it matches the default.

3 Β· identityBased with a user-assigned identity

Deliver through a specific user-assigned identity rather than the hub's system-assigned one.

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

  name                = "telemetry-eventhub"
  resource_group_name = "rg-iot-eastus"
  iothub_id           = module.iothub.id
  authentication_type = "identityBased"
  endpoint_uri        = "sb://ehns-telemetry-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, and that identity needs Azure Event Hubs Data Sender on the target Event Hub.

4 Β· keyBased with a connection string

The explicit opt-in when identity auth is not available.

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

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

  name                = "legacy-eventhub"
  resource_group_name = "rg-iot-eastus"
  iothub_id           = module.iothub.id
  authentication_type = "keyBased"
  connection_string   = var.eventhub_connection_string
}

πŸ”’ Provision connection_string out of band and pass a reference β€” never commit it to source. The input is marked sensitive = true.

5 Β· keyBased sourced from a Key Vault reference

Keep the secret in Key Vault and hand only a reference to Terraform.

data "azurerm_key_vault_secret" "eh_conn" {
  name         = "eventhub-send-connection"
  key_vault_id = var.key_vault_id
}

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

  name                = "legacy-eventhub"
  resource_group_name = "rg-iot-eastus"
  iothub_id           = module.iothub.id
  authentication_type = "keyBased"
  connection_string   = data.azurerm_key_vault_secret.eh_conn.value
}

πŸ’‘ A Key Vault data source keeps the connection string out of state files that you author and out of version control.

6 Β· Explicit subscription_id override

Register the endpoint against an Event Hub in a different subscription from the hub.

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

  name                = "cross-sub-eventhub"
  resource_group_name = "rg-iot-eastus"
  iothub_id           = module.iothub.id
  subscription_id     = "11111111-1111-1111-1111-111111111111"
  endpoint_uri        = "sb://ehns-shared-eastus.servicebus.windows.net"
  entity_path         = "device-telemetry"
}

ℹ️ When subscription_id is omitted, the endpoint defaults to the subscription of the parent IoT Hub.

7 Β· Custom timeouts

Override the provider's per-operation timeouts for a slow-provisioning environment.

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

  name                = "telemetry-eventhub"
  resource_group_name = "rg-iot-eastus"
  iothub_id           = module.iothub.id
  endpoint_uri        = "sb://ehns-telemetry-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.

8 Β· Referencing upstream namespace outputs

Wire the Event Hub namespace endpoint from a sibling module instead of hardcoding it.

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

  name                = "telemetry-eventhub"
  resource_group_name = module.resource_group.name
  iothub_id           = module.iothub.id
  endpoint_uri        = "sb://${module.eventhub_namespace.name}.servicebus.windows.net"
  entity_path         = var.eventhub_name
}

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

9 Β· Multiple endpoints with for_each

Create a keyed set of Event Hub endpoints on one hub.

locals {
  endpoints = {
    telemetry = { name = "telemetry-eventhub", entity = "device-telemetry" }
    alerts    = { name = "alerts-eventhub", entity = "device-alerts" }
    audit     = { name = "audit-eventhub", entity = "device-audit" }
  }
}

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

  name                = each.value.name
  resource_group_name = "rg-iot-eastus"
  iothub_id           = module.iothub.id
  endpoint_uri        = "sb://ehns-telemetry-eastus.servicebus.windows.net"
  entity_path         = each.value.entity
}

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

10 Β· Naming by environment

Derive endpoint names from an environment variable for repeatable multi-stage deployments.

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

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

  name                = "telemetry-eventhub-${var.environment}"
  resource_group_name = "rg-iot-${var.environment}-eastus"
  iothub_id           = module.iothub.id
  endpoint_uri        = "sb://ehns-telemetry-${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.

11 Β· Consuming the emitted id in a route

Feed the endpoint id (via its name) into an IoT Hub route that selects which messages arrive.

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

  name                = "telemetry-eventhub"
  resource_group_name = "rg-iot-eastus"
  iothub_id           = module.iothub.id
  endpoint_uri        = "sb://ehns-telemetry-eastus.servicebus.windows.net"
  entity_path         = "device-telemetry"
}

output "endpoint_id" {
  value = module.eh_endpoint.id
}

πŸ’‘ The id is emitted first by convention and is the canonical cross-resource reference in Azure; a route references the endpoint by its name.

12 Β· Mixed auth across a fleet

Most endpoints use identity auth; one legacy target stays on a connection string.

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

locals {
  endpoints = {
    telemetry = { name = "telemetry-eventhub", auth = "identityBased", entity = "device-telemetry" }
    legacy    = { name = "legacy-eventhub", auth = "keyBased", entity = null }
  }
}

module "eh_endpoint" {
  source   = "git::https://github.com/microsoftexpert/terraform-azurerm-iothub-endpoint-eventhub.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.auth == "identityBased" ? "sb://ehns-telemetry-eastus.servicebus.windows.net" : null
  entity_path         = each.value.entity
  connection_string   = each.value.auth == "keyBased" ? var.legacy_connection_string : null
}

⚠️ Keep keyBased opt-outs explicit and few. Every connection string is a standing secret; review each one on plan.

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

Wire an IoT Hub, a user-assigned identity, and an Event Hub namespace from sibling modules into this endpoint, then hand the endpoint into an IoT Hub 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-telemetry"
  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-platform-eastus"
  resource_group_name = module.resource_group.name
  location            = module.resource_group.location
  sku                 = { name = "S1", capacity = 1 }
}

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

  name                = "ehns-telemetry-eastus"
  resource_group_name = module.resource_group.name
  location            = module.resource_group.location
}

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

  name                = "telemetry-eventhub"
  resource_group_name = module.resource_group.name
  iothub_id           = module.iothub.id
  authentication_type = "identityBased"
  endpoint_uri        = "sb://${module.eventhub_namespace.name}.servicebus.windows.net"
  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-to-eventhub"
  resource_group_name = module.resource_group.name
  iothub_name         = module.iothub.name
  routing_source      = "DeviceMessages"
  condition           = "true"
  endpoint_names      = [module.eventhub_endpoint.name]
  enabled             = true
}

output "eventhub_endpoint_id" {
  value = module.eventhub_endpoint.id
}

πŸ’‘ Attribute references (module.iothub.id, module.iot_identity.id, module.eventhub_namespace.name, module.eventhub_endpoint.name) let Terraform order creation automatically β€” hub and identity, then the endpoint, then the route β€” with no depends_on. The namespace module emits its name rather than a service-bus URI, so the sb:// endpoint is composed from that name; the reference still carries the dependency.


πŸ“₯ 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.

Authentication (choose one mode)

Name Type Default Description
authentication_type string "identityBased" identityBased or keyBased. Secure default.
endpoint_uri string null Event Hubs namespace endpoint URI. Required for identityBased.
entity_path string null Event Hub name within the namespace. Required for identityBased.
identity_id string null User-assigned identity ID. Optional; identityBased only.
connection_string string (sensitive) null Event Hub connection string. Required for keyBased.

Other (optional)

Name Type Default Description
subscription_id string null Subscription of the endpoint. 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 **Event Hub** 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 Event Hubs 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 Event Hub. 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 Event Hubs namespace endpoint. Mandatory and only valid when `authentication_type` is `identityBased`."
}

variable "entity_path" {
  type        = string
  default     = null
  description = "Name of the Event Hub 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 Event Hubs 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 Event Hubs namespace URI in use on the identityBased path, read back from Azure Passthrough
entity_path Name of the Event Hub 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

🧠 Architecture Notes

  • Single keystone, no children. This module owns exactly one resource, azurerm_iothub_endpoint_eventhub.this. The parent hub, the target Event Hub, and any routing rules are sibling concerns, referenced by id or name rather than created here.
  • Force-new fields. name, resource_group_name, and iothub_id all force replacement when changed. Because the endpoint attaches to a specific hub, moving it to a different hub or renaming it destroys and recreates the endpoint β€” plan carefully on a live hub, since traffic to the endpoint is interrupted during replacement.
  • keyBased versus identityBased. The two modes are mutually exclusive on their supporting inputs. identityBased (the default) needs endpoint_uri and entity_path and uses a managed identity β€” either the specified identity_id or the hub's system-assigned identity. keyBased needs connection_string and carries a standing secret; use it only when identity auth is genuinely unavailable.
  • Secret handling. connection_string is the only secret input; it is marked sensitive = true so Terraform redacts it in plan and console output. The module emits no secret β€” the only outputs are id and name. Provision the connection string out of band (for example from Key Vault) and pass a reference.
  • 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 IoT Hub Event Hub 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)
Authentication authentication_type = "identityBased" (managed identity, no secret) set to "keyBased" and supply a connection_string
Secret exposure no secret accepted or emitted with the default connection_string (sensitive) only under keyBased
Hub / Event Hub ownership referenced by id and URI, never created here β€”
Tags not supported by this resource; none accepted β€”
  • The input that gates whether a standing secret exists defaults to the secretless value (identityBased).
  • Where a secret is unavoidable, connection_string is sensitive = true; provision it out of band and pass a reference, do not commit it.
  • The module emits no secret β€” only the endpoint id and name leave the module.

πŸš€ 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 Event Hub actually exist, whether the endpoint_uri / entity_path or connection_string resolve to a reachable target, whether the chosen identity holds Azure Event Hubs Data Sender on the Event Hub, and whether the caller's identity holds the required Microsoft.Devices/IotHubs/* permissions. 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-platform-eastus/routingEndpoints/telemetry-eventhub"
name = "telemetry-eventhub"

πŸ” Troubleshooting

Symptom Cause Fix
endpoint name is reserved on plan or apply name is one of events, operationsMonitoringEvents, fileNotifications, or $default. Choose a unique custom name that does not collide with a built-in endpoint.
connection_string is required error authentication_type = "keyBased" but no connection_string was supplied. Provide a connection_string, or switch to identityBased with endpoint_uri + entity_path.
endpoint_uri/entity_path required error authentication_type = "identityBased" but one of the two is missing. Supply both endpoint_uri and entity_path; they are mandatory for identity auth.
Messages not delivered; sender authorization failure The identity lacks data-plane access to the Event Hub. Grant Azure Event Hubs Data Sender on the target Event Hub to the hub's identity (or the identity_id).
identity_id not found / not assigned The supplied identity is not one of the IoT Hub's assigned identities. Assign the user-assigned identity to the IoT Hub first, or omit identity_id to use the system-assigned identity.
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."