diff --git a/infrastructure/modules/relay-namespace/README.md b/infrastructure/modules/relay-namespace/README.md new file mode 100644 index 00000000..dfd20b6a --- /dev/null +++ b/infrastructure/modules/relay-namespace/README.md @@ -0,0 +1,79 @@ +# relay-namespace + +Deploy an [Azure Relay namespace](https://learn.microsoft.com/en-us/azure/azure-relay/relay-what-is-it) to enable secure hybrid connections between on-premises resources and Azure services. Azure Relay facilitates communication without requiring firewall changes or intrusive changes to corporate network infrastructure. + +This module creates: +- Azure Relay namespace +- Optional private endpoint for secure connectivity +- Diagnostic settings for monitoring and logging + +**Note:** This module only creates the namespace. Hybrid connections and authorization rules should be created separately using dedicated modules to support the pattern of one namespace with many hybrid connections. + +## Private DNS Zone + +Azure Relay uses the same private DNS zone as Service Bus: `privatelink.servicebus.windows.net`. Ensure this DNS zone is configured when enabling private endpoints. + +## Terraform documentation +For the list of inputs, outputs, resources... check the [terraform module documentation](tfdocs.md). + +## Usage + +### Basic usage (without private endpoint) +```hcl +module "relay_namespace" { + source = "../../../dtos-devops-templates/infrastructure/modules/relay-namespace" + + name = "relay-${var.application}-${var.environment}" + resource_group_name = var.resource_group_name + location = var.location + + log_analytics_workspace_id = data.terraform_remote_state.audit.outputs.log_analytics_workspace_id + + tags = var.tags +} +``` + +### With private endpoint +```hcl +module "relay_namespace" { + source = "../../../dtos-devops-templates/infrastructure/modules/relay-namespace" + + name = "relay-${var.application}-${var.environment}" + resource_group_name = var.resource_group_name + location = var.location + + log_analytics_workspace_id = data.terraform_remote_state.audit.outputs.log_analytics_workspace_id + + private_endpoint_properties = { + private_endpoint_enabled = true + private_endpoint_subnet_id = module.private_endpoint_subnet.id + private_endpoint_resource_group_name = var.network_resource_group_name + private_dns_zone_ids_relay = [data.azurerm_private_dns_zone.servicebus.id] + } + + tags = var.tags +} +``` + +## Naming constraints + +The Azure Relay namespace name must follow these rules: + +| Constraint | Requirement | +|------------|-------------| +| Length | 6-50 characters | +| Start | Must start with a letter | +| End | Must end with a letter or number | +| Characters | Letters, numbers, and hyphens only | +| Uniqueness | Globally unique (creates `.servicebus.windows.net`) | + +## Outputs + +| Output | Description | +|--------|-------------| +| `name` | The name of the Relay namespace | +| `id` | The resource ID of the Relay namespace | +| `primary_connection_string` | Primary connection string (sensitive) | +| `secondary_connection_string` | Secondary connection string (sensitive) | +| `primary_key` | Primary access key (sensitive) | +| `secondary_key` | Secondary access key (sensitive) | diff --git a/infrastructure/modules/relay-namespace/main.tf b/infrastructure/modules/relay-namespace/main.tf new file mode 100644 index 00000000..fe488eed --- /dev/null +++ b/infrastructure/modules/relay-namespace/main.tf @@ -0,0 +1,52 @@ +/* -------------------------------------------------------------------------------------------------- + Azure Relay Namespace +-------------------------------------------------------------------------------------------------- */ +resource "azurerm_relay_namespace" "relay" { + name = var.name + resource_group_name = var.resource_group_name + location = var.location + sku_name = var.sku_name + + tags = var.tags +} + +/* -------------------------------------------------------------------------------------------------- + Private Endpoint Configuration for Azure Relay Namespace +-------------------------------------------------------------------------------------------------- */ +module "private_endpoint_relay" { + count = var.private_endpoint_properties.private_endpoint_enabled ? 1 : 0 + + source = "../private-endpoint" + + name = "${var.name}-azure-relay-pep" + resource_group_name = var.private_endpoint_properties.private_endpoint_resource_group_name + location = var.location + subnet_id = var.private_endpoint_properties.private_endpoint_subnet_id + + private_dns_zone_group = { + name = "${var.name}-azure-relay-pep-zone-group" + private_dns_zone_ids = var.private_endpoint_properties.private_dns_zone_ids_relay + } + + private_service_connection = { + name = "${var.name}-relay-pep-connection" + private_connection_resource_id = azurerm_relay_namespace.relay.id + subresource_names = ["namespace"] + is_manual_connection = var.private_endpoint_properties.private_service_connection_is_manual + } + + tags = var.tags +} + +/* -------------------------------------------------------------------------------------------------- + Diagnostic Settings +-------------------------------------------------------------------------------------------------- */ +module "diagnostic-settings" { + source = "../diagnostic-settings" + + name = "${var.name}-diagnostic-setting" + target_resource_id = azurerm_relay_namespace.relay.id + log_analytics_workspace_id = var.log_analytics_workspace_id + enabled_log = var.monitor_diagnostic_setting_relay_enabled_logs + enabled_metric = var.monitor_diagnostic_setting_relay_metrics +} diff --git a/infrastructure/modules/relay-namespace/output.tf b/infrastructure/modules/relay-namespace/output.tf new file mode 100644 index 00000000..33a66d50 --- /dev/null +++ b/infrastructure/modules/relay-namespace/output.tf @@ -0,0 +1,33 @@ +output "name" { + description = "The name of the Relay namespace." + value = azurerm_relay_namespace.relay.name +} + +output "id" { + description = "The ID of the Relay namespace." + value = azurerm_relay_namespace.relay.id +} + +output "primary_connection_string" { + description = "The primary connection string for the Relay namespace." + value = azurerm_relay_namespace.relay.primary_connection_string + sensitive = true +} + +output "secondary_connection_string" { + description = "The secondary connection string for the Relay namespace." + value = azurerm_relay_namespace.relay.secondary_connection_string + sensitive = true +} + +output "primary_key" { + description = "The primary access key for the Relay namespace." + value = azurerm_relay_namespace.relay.primary_key + sensitive = true +} + +output "secondary_key" { + description = "The secondary access key for the Relay namespace." + value = azurerm_relay_namespace.relay.secondary_key + sensitive = true +} diff --git a/infrastructure/modules/relay-namespace/tfdocs.md b/infrastructure/modules/relay-namespace/tfdocs.md new file mode 100644 index 00000000..1030b7a6 --- /dev/null +++ b/infrastructure/modules/relay-namespace/tfdocs.md @@ -0,0 +1,140 @@ +# Module documentation + +## Required Inputs + +The following input variables are required: + +### [location](#input\_location) + +Description: The location/region where the Relay namespace is created. + +Type: `string` + +### [log\_analytics\_workspace\_id](#input\_log\_analytics\_workspace\_id) + +Description: ID of the Log Analytics workspace to send resource logging to via diagnostic settings. + +Type: `string` + +### [name](#input\_name) + +Description: The name of the Azure Relay namespace. + +Type: `string` + +### [private\_endpoint\_properties](#input\_private\_endpoint\_properties) + +Description: Consolidated properties for the Azure Relay Private Endpoint. + +Type: + +```hcl +object({ + private_dns_zone_ids_relay = optional(list(string), []) + private_endpoint_enabled = optional(bool, false) + private_endpoint_subnet_id = optional(string, "") + private_endpoint_resource_group_name = optional(string, "") + private_service_connection_is_manual = optional(bool, false) + }) +``` + +### [resource\_group\_name](#input\_resource\_group\_name) + +Description: The name of the resource group in which to create the Relay namespace. + +Type: `string` + +## Optional Inputs + +The following input variables are optional (have default values): + +### [monitor\_diagnostic\_setting\_relay\_enabled\_logs](#input\_monitor\_diagnostic\_setting\_relay\_enabled\_logs) + +Description: Controls what logs will be enabled for the Relay namespace. + +Type: `list(string)` + +Default: + +```json +[ + "HybridConnectionsEvent" +] +``` + +### [monitor\_diagnostic\_setting\_relay\_metrics](#input\_monitor\_diagnostic\_setting\_relay\_metrics) + +Description: Controls what metrics will be enabled for the Relay namespace. + +Type: `list(string)` + +Default: + +```json +[ + "AllMetrics" +] +``` + +### [sku\_name](#input\_sku\_name) + +Description: The SKU for the Relay namespace. Only 'Standard' is supported. + +Type: `string` + +Default: `"Standard"` + +### [tags](#input\_tags) + +Description: A mapping of tags to assign to the resource. + +Type: `map(string)` + +Default: `{}` +## Modules + +The following Modules are called: + +### [diagnostic-settings](#module\_diagnostic-settings) + +Source: ../diagnostic-settings + +Version: + +### [private\_endpoint\_relay](#module\_private\_endpoint\_relay) + +Source: ../private-endpoint + +Version: +## Outputs + +The following outputs are exported: + +### [id](#output\_id) + +Description: The ID of the Relay namespace. + +### [name](#output\_name) + +Description: The name of the Relay namespace. + +### [primary\_connection\_string](#output\_primary\_connection\_string) + +Description: The primary connection string for the Relay namespace. + +### [primary\_key](#output\_primary\_key) + +Description: The primary access key for the Relay namespace. + +### [secondary\_connection\_string](#output\_secondary\_connection\_string) + +Description: The secondary connection string for the Relay namespace. + +### [secondary\_key](#output\_secondary\_key) + +Description: The secondary access key for the Relay namespace. +## Resources + +The following resources are used by this module: + +- [azurerm_relay_namespace.relay](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/relay_namespace) (resource) diff --git a/infrastructure/modules/relay-namespace/variables.tf b/infrastructure/modules/relay-namespace/variables.tf new file mode 100644 index 00000000..ad31d7b7 --- /dev/null +++ b/infrastructure/modules/relay-namespace/variables.tf @@ -0,0 +1,75 @@ +variable "name" { + description = "The name of the Azure Relay namespace." + type = string + validation { + condition = can(regex("^[a-zA-Z][a-zA-Z0-9-]{4,48}[a-zA-Z0-9]$", var.name)) + error_message = "The Relay namespace name must be 6-50 characters, start with a letter, end with alphanumeric, and contain only letters, numbers, and hyphens." + } +} + +variable "resource_group_name" { + description = "The name of the resource group in which to create the Relay namespace." + type = string +} + +variable "location" { + description = "The location/region where the Relay namespace is created." + type = string +} + +variable "sku_name" { + description = "The SKU for the Relay namespace. Only 'Standard' is supported." + type = string + default = "Standard" + + validation { + condition = var.sku_name == "Standard" + error_message = "Azure Relay namespace only supports 'Standard' SKU." + } +} + +variable "log_analytics_workspace_id" { + type = string + description = "ID of the Log Analytics workspace to send resource logging to via diagnostic settings." +} + +variable "monitor_diagnostic_setting_relay_enabled_logs" { + type = list(string) + description = "Controls what logs will be enabled for the Relay namespace." + default = ["HybridConnectionsEvent"] +} + +variable "monitor_diagnostic_setting_relay_metrics" { + type = list(string) + description = "Controls what metrics will be enabled for the Relay namespace." + default = ["AllMetrics"] +} + +variable "private_endpoint_properties" { + description = "Consolidated properties for the Azure Relay Private Endpoint." + type = object({ + private_dns_zone_ids_relay = optional(list(string), []) + private_endpoint_enabled = optional(bool, false) + private_endpoint_subnet_id = optional(string, "") + private_endpoint_resource_group_name = optional(string, "") + private_service_connection_is_manual = optional(bool, false) + }) + + validation { + condition = ( + can(var.private_endpoint_properties == null) || + can(var.private_endpoint_properties.private_endpoint_enabled == false) || + can((length(var.private_endpoint_properties.private_dns_zone_ids_relay) > 0 && + length(var.private_endpoint_properties.private_endpoint_subnet_id) > 0 + ) + ) + ) + error_message = "Both private_dns_zone_ids_relay and private_endpoint_subnet_id must be provided if private_endpoint_enabled is true." + } +} + +variable "tags" { + description = "A mapping of tags to assign to the resource." + type = map(string) + default = {} +}