Messaging & Events

Messaging & Events Security Baselines

Messaging services carry sensitive business events between distributed components. Service Bus handles enterprise messaging with queues and topics, while Event Hubs ingests high-volume event streams. Both services share similar network and identity security patterns but differ in data protection needs.

Secure Messaging Architecture

graph LR subgraph Producers["Message Producers"] APP[App Service] FUNC[Azure Functions] ONPREM[On Prem System] end subgraph Messaging["Messaging Tier"] SB[Service Bus Premium] EH[Event Hubs Premium] end subgraph Consumers["Message Consumers"] FUNC2[Function Consumer] APP2[App Consumer] SA[Storage Archive] end APP -->|Managed Identity| SB FUNC -->|Managed Identity| EH ONPREM -->|Private Network and Entra or SAS| SB SB -->|Managed Identity| FUNC2 EH -->|Managed Identity| APP2 EH -->|Capture| SA style Producers stroke:#4ade80,stroke-width:2px style Messaging stroke:#22d3ee,stroke-width:2px style Consumers stroke:#a855f7,stroke-width:2px

Azure Service Bus

Enterprise message broker with queues, topics/subscriptions, and sessions. Service Bus Premium runs on dedicated infrastructure with VNet integration, CMK encryption, and zone redundancy — always use Premium tier for production workloads requiring security isolation.

PropertyValue
Defender PlanDefender for Resource Manager (control plane protection)
Azure Policy Built-inYes — Service Bus initiative (TLS, auth, network, CMK)
Diagnostic LogsOperationalLogs, VNetAndIPFilteringLogs, RuntimeAuditLogs
Recommended SKUPremium (VNet integration, CMK, zone redundancy, dedicated resources)
Key ConsiderationMessages may contain PII/PHI — treat Service Bus as a data store for security classification

Baseline Controls

NS✓ Supported

Private Endpoint

○ ManualCustomer
NS✓ Supported

Disable Public Access

○ ManualCustomer
NS✓ Supported

TLS 1.2 Minimum

● DefaultMicrosoft
IM✓ Supported

Entra ID RBAC

○ ManualCustomer
IM✓ Supported

Disable SAS/Key Auth

○ ManualCustomer
DP✓ Supported

Encryption at Rest

● DefaultMicrosoft
DP✓ Supported

CMK (Premium)

○ ManualCustomer
LT✓ Supported

Diagnostic Settings

○ ManualCustomer
BR✓ Supported

Geo-Disaster Recovery

○ ManualCustomer

Terraform: Hardened Service Bus Premium

hcl
resource "azurerm_servicebus_namespace" "main" {
  name                = "sb-messaging-prod"
  resource_group_name = azurerm_resource_group.messaging.name
  location            = azurerm_resource_group.messaging.location
  sku                 = "Premium"              # Required for VNet, CMK, zone redundancy
  capacity            = 1                      # Messaging units
  premium_messaging_partitions = 1
  zone_redundant      = true                   # BR: Zone redundancy

  # NS-2: Disable public network access
  public_network_access_enabled = false

  # NS-8: TLS 1.2 minimum
  minimum_tls_version = "1.2"

  # IM-1: Disable SAS/key-based auth — Entra only
  local_auth_enabled = false

  # DP-5: CMK encryption
  customer_managed_key {
    key_vault_key_id                  = azurerm_key_vault_key.sb_cmk.id
    identity_id                       = azurerm_user_assigned_identity.sb.id
    infrastructure_encryption_enabled = true    # Double encryption
  }

  identity {
    type         = "UserAssigned"
    identity_ids = [azurerm_user_assigned_identity.sb.id]
  }
}

# Queue with dead letter monitoring
resource "azurerm_servicebus_queue" "orders" {
  name         = "orders"
  namespace_id = azurerm_servicebus_namespace.main.id

  max_delivery_count         = 10
  dead_lettering_on_message_expiration = true
  requires_duplicate_detection = true
  duplicate_detection_history_time_window = "PT10M"

  # Messages auto-expire after 14 days (data retention limit)
  default_message_ttl = "P14D"
}

# Topic with subscription filtering
resource "azurerm_servicebus_topic" "events" {
  name         = "business-events"
  namespace_id = azurerm_servicebus_namespace.main.id

  max_size_in_megabytes        = 5120
  requires_duplicate_detection = true
  default_message_ttl          = "P7D"
}

# NS-2: Private Endpoint
resource "azurerm_private_endpoint" "sb" {
  name                = "pe-sb-prod"
  location            = azurerm_resource_group.messaging.location
  resource_group_name = azurerm_resource_group.messaging.name
  subnet_id           = azurerm_subnet.messaging.id

  private_service_connection {
    name                           = "psc-sb-prod"
    private_connection_resource_id = azurerm_servicebus_namespace.main.id
    subresource_names              = ["namespace"]
    is_manual_connection           = false
  }

  private_dns_zone_group {
    name                 = "pdz-sb"
    private_dns_zone_ids = [azurerm_private_dns_zone.servicebus.id]
  }
}

# IM: RBAC role assignment for sending application
resource "azurerm_role_assignment" "app_sb_sender" {
  scope                = azurerm_servicebus_queue.orders.id
  role_definition_name = "Azure Service Bus Data Sender"
  principal_id         = azurerm_linux_web_app.api.identity[0].principal_id
}

resource "azurerm_role_assignment" "func_sb_receiver" {
  scope                = azurerm_servicebus_queue.orders.id
  role_definition_name = "Azure Service Bus Data Receiver"
  principal_id         = azurerm_linux_function_app.processor.identity[0].principal_id
}

# LT-3: Diagnostic Settings
resource "azurerm_monitor_diagnostic_setting" "sb" {
  name                       = "diag-sb-prod"
  target_resource_id         = azurerm_servicebus_namespace.main.id
  log_analytics_workspace_id = azurerm_log_analytics_workspace.main.id

  enabled_log { category = "OperationalLogs" }
  enabled_log { category = "VNetAndIPFilteringLogs" }
  enabled_log { category = "RuntimeAuditLogs" }

  metric { category = "AllMetrics" }
}
resource "azurerm_servicebus_namespace" "main" {
  name                = "sb-messaging-prod"
  resource_group_name = azurerm_resource_group.messaging.name
  location            = azurerm_resource_group.messaging.location
  sku                 = "Premium"              # Required for VNet, CMK, zone redundancy
  capacity            = 1                      # Messaging units
  premium_messaging_partitions = 1
  zone_redundant      = true                   # BR: Zone redundancy

  # NS-2: Disable public network access
  public_network_access_enabled = false

  # NS-8: TLS 1.2 minimum
  minimum_tls_version = "1.2"

  # IM-1: Disable SAS/key-based auth — Entra only
  local_auth_enabled = false

  # DP-5: CMK encryption
  customer_managed_key {
    key_vault_key_id                  = azurerm_key_vault_key.sb_cmk.id
    identity_id                       = azurerm_user_assigned_identity.sb.id
    infrastructure_encryption_enabled = true    # Double encryption
  }

  identity {
    type         = "UserAssigned"
    identity_ids = [azurerm_user_assigned_identity.sb.id]
  }
}

# Queue with dead letter monitoring
resource "azurerm_servicebus_queue" "orders" {
  name         = "orders"
  namespace_id = azurerm_servicebus_namespace.main.id

  max_delivery_count         = 10
  dead_lettering_on_message_expiration = true
  requires_duplicate_detection = true
  duplicate_detection_history_time_window = "PT10M"

  # Messages auto-expire after 14 days (data retention limit)
  default_message_ttl = "P14D"
}

# Topic with subscription filtering
resource "azurerm_servicebus_topic" "events" {
  name         = "business-events"
  namespace_id = azurerm_servicebus_namespace.main.id

  max_size_in_megabytes        = 5120
  requires_duplicate_detection = true
  default_message_ttl          = "P7D"
}

# NS-2: Private Endpoint
resource "azurerm_private_endpoint" "sb" {
  name                = "pe-sb-prod"
  location            = azurerm_resource_group.messaging.location
  resource_group_name = azurerm_resource_group.messaging.name
  subnet_id           = azurerm_subnet.messaging.id

  private_service_connection {
    name                           = "psc-sb-prod"
    private_connection_resource_id = azurerm_servicebus_namespace.main.id
    subresource_names              = ["namespace"]
    is_manual_connection           = false
  }

  private_dns_zone_group {
    name                 = "pdz-sb"
    private_dns_zone_ids = [azurerm_private_dns_zone.servicebus.id]
  }
}

# IM: RBAC role assignment for sending application
resource "azurerm_role_assignment" "app_sb_sender" {
  scope                = azurerm_servicebus_queue.orders.id
  role_definition_name = "Azure Service Bus Data Sender"
  principal_id         = azurerm_linux_web_app.api.identity[0].principal_id
}

resource "azurerm_role_assignment" "func_sb_receiver" {
  scope                = azurerm_servicebus_queue.orders.id
  role_definition_name = "Azure Service Bus Data Receiver"
  principal_id         = azurerm_linux_function_app.processor.identity[0].principal_id
}

# LT-3: Diagnostic Settings
resource "azurerm_monitor_diagnostic_setting" "sb" {
  name                       = "diag-sb-prod"
  target_resource_id         = azurerm_servicebus_namespace.main.id
  log_analytics_workspace_id = azurerm_log_analytics_workspace.main.id

  enabled_log { category = "OperationalLogs" }
  enabled_log { category = "VNetAndIPFilteringLogs" }
  enabled_log { category = "RuntimeAuditLogs" }

  metric { category = "AllMetrics" }
}

Azure Portal Instructions

Step-by-step instructions for configuring each Service Bus control through the Azure Portal.

NS-1 — Service Endpoints
  1. Navigate to your Service Bus namespace in the Azure Portal
  2. In the left menu under Settings, select Networking
  3. Select the Selected networks option
  4. Under Virtual networks, click + Add existing virtual network
  5. Select the VNet and subnet to allow access from
  6. Under Firewall, optionally add allowed client IP addresses
  7. Click Save
NS-2 — Private Endpoints
  1. Navigate to your Service Bus namespace in the Azure Portal
  2. In the left menu under Settings, select Networking
  3. Select the Private endpoint connections tab
  4. Click + Private endpoint to create a new private endpoint
  5. Select the VNet, subnet, and configure the private DNS zone (privatelink.servicebus.windows.net)
  6. Go back to the Public access tab and set it to Disabled
  7. Click Save
IM-1 — Entra ID Authentication
  1. Navigate to your Service Bus namespace in the Azure Portal
  2. In the left menu, select Access control (IAM)
  3. Click + Add role assignment
  4. Assign "Azure Service Bus Data Sender" and/or "Azure Service Bus Data Receiver" roles to users, groups, or managed identities
  5. To disable SAS/local authentication, go to Settings > Shared access policies
  6. Note: Disabling local auth requires ARM/CLI (set disableLocalAuth=true) — the portal does not yet expose this toggle directly
IM-3 — Managed Identities
  1. On the application side (e.g., App Service or Function App), enable Managed Identity under Settings > Identity
  2. Navigate to your Service Bus namespace in the Azure Portal
  3. Go to Access control (IAM)
  4. Click + Add role assignment
  5. Assign "Azure Service Bus Data Sender" or "Azure Service Bus Data Receiver" to the application managed identity
  6. Update the application code to use DefaultAzureCredential instead of connection strings
DP-3 — Data in Transit Encryption

This feature is managed by Microsoft and enabled by default. All AMQP and HTTPS connections use TLS 1.2 automatically. No portal configuration is required.

DP-4 — Data at Rest Encryption

This feature is managed by Microsoft and enabled by default. No portal configuration is required — all messages, queues, and topics are encrypted at rest.

DP-5 — Encryption with CMK
  1. CMK is available on Premium tier only
  2. Navigate to your Service Bus namespace (Premium) in the Azure Portal
  3. In the left menu under Settings, select Encryption
  4. Select Customer-managed key
  5. Select the Key Vault and encryption key
  6. Choose or configure the managed identity for Key Vault access
  7. Click Save
  8. Note: For new namespaces, CMK can be configured during the creation wizard on the Encryption tab
LT-3 — Diagnostic Logging
  1. Navigate to your Service Bus namespace in the Azure Portal
  2. In the left menu under Monitoring, select Diagnostic settings
  3. Click + Add diagnostic setting
  4. Give the setting a name
  5. Select log categories: OperationalLogs, VNetAndIPFilteringLogs, RuntimeAuditLogs
  6. Select the destination: Send to Log Analytics workspace
  7. Select your Log Analytics workspace
  8. Click Save
LT-1 — Defender for Resource Manager
  1. Navigate to Microsoft Defender for Cloud in the Azure Portal
  2. In the left menu, select Environment settings
  3. Select your subscription
  4. Under Cloud Workload Protection (CWP), find the Resource Manager row
  5. Toggle the plan to On
  6. Click Save — this monitors control plane operations across all resources including Service Bus

SAS Keys Are Legacy

Service Bus connection strings with SAS keys bypass Entra ID and Conditional Access completely. In production, set local_auth_enabled = false and use Managed Identity with Azure Service Bus Data Sender/Receiver RBAC roles instead. Split sender and receiver roles per least privilege.

Service Bus Security Design Patterns

Dead Letter Monitoring

Monitor dead letter queues as a security signal. Unexpected message failures may indicate poisoned messages, format injection attempts, or deserialization attacks. Alert when DLQ count exceeds threshold.

Message TTL as Data Retention

Set default_message_ttl aligned to your data retention policy. Messages containing PII should not persist indefinitely. Expired messages can be routed to dead letter for audit-compliant disposal.

Separate Send/Receive Identities

Use distinct Managed Identities for producers and consumers with separate RBAC assignments. A compromised consumer should not be able to inject messages, and a compromised producer should not be able to read them.

Message Payload Encryption

For highly sensitive data (PII, PHI, financial), encrypt the message payload at the application layer before sending. Service Bus encryption at rest protects the broker storage, but application-layer encryption protects against broker-level compromise.

Azure Event Hubs

Big data streaming platform and event ingestion service capable of millions of events per second. Event Hubs shares many security patterns with Service Bus as both use the same underlying AMQP infrastructure, but adds Kafka compatibility and Capture for long-term archival.

PropertyValue
Defender PlanDefender for Resource Manager (control plane)
Azure Policy Built-inYes — Event Hubs initiative
Diagnostic LogsOperationalLogs, AutoScaleLogs, KafkaCoordinatorLogs, RuntimeAuditLogs
Recommended SKUPremium or Dedicated (VNet integration, CMK)

Baseline Controls

NS✓ Supported

Private Endpoint

○ ManualCustomer
NS✓ Supported

Disable Public Access

○ ManualCustomer
IM✓ Supported

Entra ID RBAC

○ ManualCustomer
IM✓ Supported

Disable SAS Auth

○ ManualCustomer
DP✓ Supported

Encryption at Rest

● DefaultMicrosoft
DP✓ Supported

CMK (Premium/Ded)

○ ManualCustomer
LT✓ Supported

Diagnostic Settings

○ ManualCustomer
BR✓ Supported

Event Capture

○ ManualCustomer

Terraform: Hardened Event Hubs Premium

hcl
resource "azurerm_eventhub_namespace" "main" {
  name                = "evhns-telemetry-prod"
  resource_group_name = azurerm_resource_group.messaging.name
  location            = azurerm_resource_group.messaging.location
  sku                 = "Premium"
  capacity            = 1
  zone_redundant      = true

  # NS-2: Disable public access
  public_network_access_enabled = false

  # NS-8: TLS 1.2 minimum
  minimum_tls_version = "1.2"

  # IM-1: Disable SAS/key auth
  local_authentication_enabled = false

  identity {
    type = "SystemAssigned"
  }
}

resource "azurerm_eventhub" "telemetry" {
  name             = "telemetry-events"
  namespace_id     = azurerm_eventhub_namespace.main.id
  partition_count  = 8
  message_retention = 7                       # Retention days

  # BR: Capture events to immutable storage
  capture_description {
    enabled             = true
    encoding            = "Avro"
    interval_in_seconds = 300
    size_limit_in_bytes = 314572800

    destination {
      name                = "EventHubArchive"
      archive_name_format = "{Namespace}/{EventHub}/{PartitionId}/{Year}/{Month}/{Day}/{Hour}/{Minute}/{Second}"
      blob_container_name = azurerm_storage_container.capture.name
      storage_account_id  = azurerm_storage_account.archive.id
    }
  }
}

# NS-2: Private Endpoint
resource "azurerm_private_endpoint" "eh" {
  name                = "pe-evhns-prod"
  location            = azurerm_resource_group.messaging.location
  resource_group_name = azurerm_resource_group.messaging.name
  subnet_id           = azurerm_subnet.messaging.id

  private_service_connection {
    name                           = "psc-evhns-prod"
    private_connection_resource_id = azurerm_eventhub_namespace.main.id
    subresource_names              = ["namespace"]
    is_manual_connection           = false
  }
}

# IM: Least privilege RBAC
resource "azurerm_role_assignment" "producer_eh" {
  scope                = azurerm_eventhub.telemetry.id
  role_definition_name = "Azure Event Hubs Data Sender"
  principal_id         = azurerm_linux_web_app.telemetry_api.identity[0].principal_id
}

resource "azurerm_role_assignment" "consumer_eh" {
  scope                = azurerm_eventhub.telemetry.id
  role_definition_name = "Azure Event Hubs Data Receiver"
  principal_id         = azurerm_linux_function_app.telemetry_processor.identity[0].principal_id
}
resource "azurerm_eventhub_namespace" "main" {
  name                = "evhns-telemetry-prod"
  resource_group_name = azurerm_resource_group.messaging.name
  location            = azurerm_resource_group.messaging.location
  sku                 = "Premium"
  capacity            = 1
  zone_redundant      = true

  # NS-2: Disable public access
  public_network_access_enabled = false

  # NS-8: TLS 1.2 minimum
  minimum_tls_version = "1.2"

  # IM-1: Disable SAS/key auth
  local_authentication_enabled = false

  identity {
    type = "SystemAssigned"
  }
}

resource "azurerm_eventhub" "telemetry" {
  name             = "telemetry-events"
  namespace_id     = azurerm_eventhub_namespace.main.id
  partition_count  = 8
  message_retention = 7                       # Retention days

  # BR: Capture events to immutable storage
  capture_description {
    enabled             = true
    encoding            = "Avro"
    interval_in_seconds = 300
    size_limit_in_bytes = 314572800

    destination {
      name                = "EventHubArchive"
      archive_name_format = "{Namespace}/{EventHub}/{PartitionId}/{Year}/{Month}/{Day}/{Hour}/{Minute}/{Second}"
      blob_container_name = azurerm_storage_container.capture.name
      storage_account_id  = azurerm_storage_account.archive.id
    }
  }
}

# NS-2: Private Endpoint
resource "azurerm_private_endpoint" "eh" {
  name                = "pe-evhns-prod"
  location            = azurerm_resource_group.messaging.location
  resource_group_name = azurerm_resource_group.messaging.name
  subnet_id           = azurerm_subnet.messaging.id

  private_service_connection {
    name                           = "psc-evhns-prod"
    private_connection_resource_id = azurerm_eventhub_namespace.main.id
    subresource_names              = ["namespace"]
    is_manual_connection           = false
  }
}

# IM: Least privilege RBAC
resource "azurerm_role_assignment" "producer_eh" {
  scope                = azurerm_eventhub.telemetry.id
  role_definition_name = "Azure Event Hubs Data Sender"
  principal_id         = azurerm_linux_web_app.telemetry_api.identity[0].principal_id
}

resource "azurerm_role_assignment" "consumer_eh" {
  scope                = azurerm_eventhub.telemetry.id
  role_definition_name = "Azure Event Hubs Data Receiver"
  principal_id         = azurerm_linux_function_app.telemetry_processor.identity[0].principal_id
}

Azure Portal Instructions

Step-by-step instructions for configuring each Event Hubs control through the Azure Portal.

NS-2 — Private Endpoints
  1. Navigate to your Event Hubs namespace in the Azure Portal
  2. In the left menu under Settings, select Networking
  3. Select the Private endpoint connections tab
  4. Click + Private endpoint to create a new private endpoint
  5. Select the VNet, subnet, and configure the private DNS zone (privatelink.servicebus.windows.net)
  6. Go back to the Public access tab and set it to Disabled
  7. Click Save
IM-1 — Entra ID Authentication
  1. Navigate to your Event Hubs namespace in the Azure Portal
  2. In the left menu, select Access control (IAM)
  3. Click + Add role assignment
  4. Assign "Azure Event Hubs Data Sender" and/or "Azure Event Hubs Data Receiver" roles to users, groups, or managed identities
  5. To disable SAS/local authentication, this requires ARM/CLI (set disableLocalAuth=true)
IM-3 — Managed Identities
  1. On the application side (e.g., App Service or Function App), enable Managed Identity under Settings > Identity
  2. Navigate to your Event Hubs namespace in the Azure Portal
  3. Go to Access control (IAM)
  4. Click + Add role assignment
  5. Assign "Azure Event Hubs Data Sender" or "Azure Event Hubs Data Receiver" to the application managed identity
  6. Update the application to use DefaultAzureCredential instead of connection strings
DP-3 — Data in Transit Encryption

This feature is managed by Microsoft and enabled by default. TLS 1.2 is enforced for all AMQP and HTTPS connections automatically. No portal configuration is required.

DP-4 — Data at Rest Encryption

This feature is managed by Microsoft and enabled by default. No portal configuration is required — all event data including captured events is encrypted at rest.

DP-5 — Encryption with CMK
  1. CMK is available on Premium and Dedicated tiers only
  2. Navigate to your Event Hubs namespace (Premium/Dedicated) in the Azure Portal
  3. In the left menu under Settings, select Encryption
  4. Select Customer-managed key
  5. Select the Key Vault and encryption key
  6. Choose or configure the managed identity for Key Vault access
  7. Click Save
  8. Note: For new namespaces, CMK can be configured during creation on the Encryption tab
LT-3 — Diagnostic Logging
  1. Navigate to your Event Hubs namespace in the Azure Portal
  2. In the left menu under Monitoring, select Diagnostic settings
  3. Click + Add diagnostic setting
  4. Give the setting a name
  5. Select log categories: OperationalLogs, ArchiveLogs, AutoScaleLogs, KafkaUserErrorLogs
  6. Select the destination: Send to Log Analytics workspace
  7. Select your Log Analytics workspace
  8. Click Save

Capture Security

Event Hub Capture writes to a Storage Account — ensure that storage account follows the Storage Account baseline: private endpoint, disabled shared key, CMK encryption, and immutable blob policies for compliance-grade archival.