diff --git a/infrastructure/modules/relay-hybrid-connection/README.md b/infrastructure/modules/relay-hybrid-connection/README.md new file mode 100644 index 00000000..d5022dc0 --- /dev/null +++ b/infrastructure/modules/relay-hybrid-connection/README.md @@ -0,0 +1,126 @@ +# relay-hybrid-connection + +Deploy an [Azure Relay Hybrid Connection](https://learn.microsoft.com/en-us/azure/azure-relay/relay-hybrid-connections-protocol) to enable secure, bi-directional communication between on-premises applications and Azure services through an existing Azure Relay namespace. + +This module creates: +- Azure Relay Hybrid Connection +- Optional authorization rules with configurable permissions (Listen, Send, Manage) + +**Note:** This module requires an existing Azure Relay namespace. Use the `relay-namespace` module to create the namespace first. + +## Terraform documentation +For the list of inputs, outputs, resources... check the [terraform module documentation](tfdocs.md). + +## Usage + +### Basic usage (without authorization rules) +```hcl +module "relay_hybrid_connection" { + source = "../../../dtos-devops-templates/infrastructure/modules/relay-hybrid-connection" + + name = "hc-${var.application}-${var.environment}" + relay_namespace_name = module.relay_namespace.name + resource_group_name = var.resource_group_name +} +``` + +### With authorization rules +```hcl +module "relay_hybrid_connection" { + source = "../../../dtos-devops-templates/infrastructure/modules/relay-hybrid-connection" + + name = "hc-${var.application}-${var.environment}" + relay_namespace_name = module.relay_namespace.name + resource_group_name = var.resource_group_name + requires_client_authorization = true + user_metadata = "Application hybrid connection" + + authorization_rules = { + "listen-rule" = { + listen = true + send = false + manage = false + } + "send-rule" = { + listen = false + send = true + manage = false + } + "manage-rule" = { + listen = true + send = true + manage = true + } + } +} +``` + +### Complete example with namespace +```hcl +# Create the relay namespace first +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 +} + +# Create hybrid connection with authorization rules +module "relay_hybrid_connection" { + source = "../../../dtos-devops-templates/infrastructure/modules/relay-hybrid-connection" + + name = "hc-${var.application}-${var.environment}" + relay_namespace_name = module.relay_namespace.name + resource_group_name = var.resource_group_name + + authorization_rules = { + "app-listener" = { + listen = true + } + "app-sender" = { + send = true + } + } +} +``` + +## Naming constraints + +The Azure Relay Hybrid Connection name must follow these rules: + +| Constraint | Requirement | +|------------|-------------| +| Length | 1-260 characters | +| Start | Must start with a letter or number | +| End | Must end with a letter or number | +| Characters | Letters, numbers, hyphens, underscores, and periods | + +Authorization rule names follow similar constraints (1-256 characters). + +## Authorization rules + +Authorization rules support three permission types: + +| Permission | Description | +|------------|-------------| +| `listen` | Allows receiving messages from the hybrid connection | +| `send` | Allows sending messages to the hybrid connection | +| `manage` | Allows managing the hybrid connection (requires both `listen` and `send` to be `true`) | + +**Note:** When `manage = true`, both `listen` and `send` must also be set to `true`. The module validates this constraint. + +## Outputs + +| Output | Description | +|--------|-------------| +| `name` | The name of the Hybrid Connection | +| `id` | The resource ID of the Hybrid Connection | +| `authorization_rule_ids` | Map of authorization rule names to their IDs | +| `authorization_rule_primary_keys` | Map of authorization rule names to their primary keys (sensitive) | +| `authorization_rule_secondary_keys` | Map of authorization rule names to their secondary keys (sensitive) | +| `authorization_rule_primary_connection_strings` | Map of authorization rule names to their primary connection strings (sensitive) | +| `authorization_rule_secondary_connection_strings` | Map of authorization rule names to their secondary connection strings (sensitive) | diff --git a/infrastructure/modules/relay-hybrid-connection/main.tf b/infrastructure/modules/relay-hybrid-connection/main.tf new file mode 100644 index 00000000..f5ec5b97 --- /dev/null +++ b/infrastructure/modules/relay-hybrid-connection/main.tf @@ -0,0 +1,26 @@ +/* -------------------------------------------------------------------------------------------------- + Azure Relay Hybrid Connection +-------------------------------------------------------------------------------------------------- */ +resource "azurerm_relay_hybrid_connection" "hybrid_connection" { + name = var.name + relay_namespace_name = var.relay_namespace_name + resource_group_name = var.resource_group_name + requires_client_authorization = var.requires_client_authorization + user_metadata = var.user_metadata +} + +/* -------------------------------------------------------------------------------------------------- + Azure Relay Hybrid Connection Authorization Rules +-------------------------------------------------------------------------------------------------- */ +resource "azurerm_relay_hybrid_connection_authorization_rule" "auth_rule" { + for_each = var.authorization_rules + + name = each.key + hybrid_connection_name = azurerm_relay_hybrid_connection.hybrid_connection.name + namespace_name = var.relay_namespace_name + resource_group_name = var.resource_group_name + + listen = each.value.listen + send = each.value.send + manage = each.value.manage +} diff --git a/infrastructure/modules/relay-hybrid-connection/output.tf b/infrastructure/modules/relay-hybrid-connection/output.tf new file mode 100644 index 00000000..6fed148b --- /dev/null +++ b/infrastructure/modules/relay-hybrid-connection/output.tf @@ -0,0 +1,38 @@ +output "id" { + description = "The ID of the Relay Hybrid Connection." + value = azurerm_relay_hybrid_connection.hybrid_connection.id +} + +output "name" { + description = "The name of the Relay Hybrid Connection." + value = azurerm_relay_hybrid_connection.hybrid_connection.name +} + +output "authorization_rule_ids" { + description = "A map of authorization rule names to their IDs." + value = { for k, v in azurerm_relay_hybrid_connection_authorization_rule.auth_rule : k => v.id } +} + +output "authorization_rule_primary_keys" { + description = "A map of authorization rule names to their primary keys." + value = { for k, v in azurerm_relay_hybrid_connection_authorization_rule.auth_rule : k => v.primary_key } + sensitive = true +} + +output "authorization_rule_secondary_keys" { + description = "A map of authorization rule names to their secondary keys." + value = { for k, v in azurerm_relay_hybrid_connection_authorization_rule.auth_rule : k => v.secondary_key } + sensitive = true +} + +output "authorization_rule_primary_connection_strings" { + description = "A map of authorization rule names to their primary connection strings." + value = { for k, v in azurerm_relay_hybrid_connection_authorization_rule.auth_rule : k => v.primary_connection_string } + sensitive = true +} + +output "authorization_rule_secondary_connection_strings" { + description = "A map of authorization rule names to their secondary connection strings." + value = { for k, v in azurerm_relay_hybrid_connection_authorization_rule.auth_rule : k => v.secondary_connection_string } + sensitive = true +} diff --git a/infrastructure/modules/relay-hybrid-connection/tfdocs.md b/infrastructure/modules/relay-hybrid-connection/tfdocs.md new file mode 100644 index 00000000..5ccc9f89 --- /dev/null +++ b/infrastructure/modules/relay-hybrid-connection/tfdocs.md @@ -0,0 +1,97 @@ +# Module documentation + +## Required Inputs + +The following input variables are required: + +### [name](#input\_name) + +Description: The name of the Azure Relay Hybrid Connection. + +Type: `string` + +### [relay\_namespace\_name](#input\_relay\_namespace\_name) + +Description: The name of the Azure Relay namespace in which to create the Hybrid Connection. + +Type: `string` + +### [resource\_group\_name](#input\_resource\_group\_name) + +Description: The name of the resource group containing the Azure Relay namespace. + +Type: `string` + +## Optional Inputs + +The following input variables are optional (have default values): + +### [authorization\_rules](#input\_authorization\_rules) + +Description: A map of authorization rules to create for the Hybrid Connection. + +Type: + +```hcl +map(object({ + listen = optional(bool, false) + send = optional(bool, false) + manage = optional(bool, false) + })) +``` + +Default: `{}` + +### [requires\_client\_authorization](#input\_requires\_client\_authorization) + +Description: Whether client authorization is required for this Hybrid Connection. + +Type: `bool` + +Default: `true` + +### [user\_metadata](#input\_user\_metadata) + +Description: User metadata string for the Hybrid Connection. + +Type: `string` + +Default: `null` + +## Outputs + +The following outputs are exported: + +### [authorization\_rule\_ids](#output\_authorization\_rule\_ids) + +Description: A map of authorization rule names to their IDs. + +### [authorization\_rule\_primary\_connection\_strings](#output\_authorization\_rule\_primary\_connection\_strings) + +Description: A map of authorization rule names to their primary connection strings. + +### [authorization\_rule\_primary\_keys](#output\_authorization\_rule\_primary\_keys) + +Description: A map of authorization rule names to their primary keys. + +### [authorization\_rule\_secondary\_connection\_strings](#output\_authorization\_rule\_secondary\_connection\_strings) + +Description: A map of authorization rule names to their secondary connection strings. + +### [authorization\_rule\_secondary\_keys](#output\_authorization\_rule\_secondary\_keys) + +Description: A map of authorization rule names to their secondary keys. + +### [id](#output\_id) + +Description: The ID of the Relay Hybrid Connection. + +### [name](#output\_name) + +Description: The name of the Relay Hybrid Connection. +## Resources + +The following resources are used by this module: + +- [azurerm_relay_hybrid_connection.hybrid_connection](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/relay_hybrid_connection) (resource) +- [azurerm_relay_hybrid_connection_authorization_rule.auth_rule](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/relay_hybrid_connection_authorization_rule) (resource) diff --git a/infrastructure/modules/relay-hybrid-connection/variables.tf b/infrastructure/modules/relay-hybrid-connection/variables.tf new file mode 100644 index 00000000..7cf17c6a --- /dev/null +++ b/infrastructure/modules/relay-hybrid-connection/variables.tf @@ -0,0 +1,56 @@ +variable "name" { + description = "The name of the Azure Relay Hybrid Connection." + type = string + validation { + condition = can(regex("^[a-zA-Z0-9]([a-zA-Z0-9-._]{0,258}[a-zA-Z0-9])?$", var.name)) + error_message = "The hybrid connection name must be 1-260 characters, start and end with an alphanumeric character, and can only contain letters, numbers, hyphens, underscores, and periods." + } +} + +variable "relay_namespace_name" { + description = "The name of the Azure Relay namespace in which to create the Hybrid Connection." + type = string +} + +variable "resource_group_name" { + description = "The name of the resource group containing the Azure Relay namespace." + type = string +} + +variable "requires_client_authorization" { + description = "Whether client authorization is required for this Hybrid Connection." + type = bool + default = true +} + +variable "user_metadata" { + description = "User metadata string for the Hybrid Connection." + type = string + default = null +} + +variable "authorization_rules" { + description = "A map of authorization rules to create for the Hybrid Connection." + type = map(object({ + listen = optional(bool, false) + send = optional(bool, false) + manage = optional(bool, false) + })) + default = {} + + validation { + condition = alltrue([ + for name, rule in var.authorization_rules : + can(regex("^[a-zA-Z0-9]([a-zA-Z0-9-._]{0,254}[a-zA-Z0-9])?$", name)) + ]) + error_message = "Authorization rule names must be 1-256 characters, start and end with an alphanumeric character, and can only contain letters, numbers, hyphens, underscores, and periods." + } + + validation { + condition = alltrue([ + for name, rule in var.authorization_rules : + rule.manage == false || (rule.listen == true && rule.send == true) + ]) + error_message = "When 'manage' is true, both 'listen' and 'send' must also be true." + } +} diff --git a/infrastructure/modules/relay-namespace/README.md b/infrastructure/modules/relay-namespace/README.md index dfd20b6a..7c6d72a0 100644 --- a/infrastructure/modules/relay-namespace/README.md +++ b/infrastructure/modules/relay-namespace/README.md @@ -7,7 +7,7 @@ This module creates: - 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. +**Note:** This module only creates the namespace. Hybrid connections and authorization rules should be created separately using the [relay-hybrid-connection](../relay-hybrid-connection/README.md) module to support the pattern of one namespace with many hybrid connections. ## Private DNS Zone