diff --git a/.azuredevops/modulePipelines/ms.relay.namespaces.yml b/.azuredevops/modulePipelines/ms.relay.namespaces.yml new file mode 100644 index 0000000000..61892df9cf --- /dev/null +++ b/.azuredevops/modulePipelines/ms.relay.namespaces.yml @@ -0,0 +1,51 @@ +name: 'Relay - Namespaces' + +parameters: + - name: staticValidation + displayName: Execute static validation + type: boolean + default: true + - name: deploymentValidation + displayName: Execute deployment validation + type: boolean + default: true + - name: removeDeployment + displayName: Remove deployed module + type: boolean + default: true + - name: prerelease + displayName: Publish prerelease module + type: boolean + default: false + +pr: none + +trigger: + batch: true + branches: + include: + - main + paths: + include: + - '/modules/relay/namespaces/*' + - '/modules/network/private-endpoints/*' + - '/.azuredevops/modulePipelines/ms.relay.namespaces.yml' + - '/.azuredevops/pipelineTemplates/*.yml' + - '/utilities/pipelines/*' + exclude: + - '/utilities/pipelines/deploymentRemoval/*' + - '/**/*.md' + +variables: + - template: '../../settings.yml' + - group: 'PLATFORM_VARIABLES' + - name: modulePath + value: '/modules/relay/namespaces' + +stages: + - template: /.azuredevops/pipelineTemplates/stages.module.yml + parameters: + staticValidation: '${{ parameters.staticValidation }}' + deploymentValidation: '${{ parameters.deploymentValidation }}' + removeDeployment: '${{ parameters.removeDeployment }}' + prerelease: '${{ parameters.prerelease }}' diff --git a/.github/workflows/ms.relay.namespaces.yml b/.github/workflows/ms.relay.namespaces.yml new file mode 100644 index 0000000000..169c72e145 --- /dev/null +++ b/.github/workflows/ms.relay.namespaces.yml @@ -0,0 +1,85 @@ +name: 'Relay - Namespaces' + +on: + workflow_dispatch: + inputs: + staticValidation: + type: boolean + description: 'Execute static validation' + required: false + default: true + deploymentValidation: + type: boolean + description: 'Execute deployment validation' + required: false + default: true + removeDeployment: + type: boolean + description: 'Remove deployed module' + required: false + default: true + prerelease: + type: boolean + description: 'Publish prerelease module' + required: false + default: false + push: + branches: + - main + paths: + - 'modules/relay/namespaces/**' + - 'modules/network/private-endpoints/**' + - '.github/actions/templates/**' + - '.github/workflows/template.module.yml' + - '.github/workflows/ms.relay.namespaces.yml' + - 'utilities/pipelines/**' + - '!utilities/pipelines/deploymentRemoval/**' + - '!*/**/README.md' + +env: + modulePath: 'modules/relay/namespaces' + workflowPath: '.github/workflows/ms.relay.namespaces.yml' + +concurrency: + group: ${{ github.workflow }} + +jobs: + ########################### + # Initialize pipeline # + ########################### + job_initialize_pipeline: + runs-on: ubuntu-20.04 + name: 'Initialize pipeline' + steps: + - name: 'Checkout' + uses: actions/checkout@v3 + with: + fetch-depth: 0 + - name: 'Set input parameters to output variables' + id: get-workflow-param + uses: ./.github/actions/templates/getWorkflowInput + with: + workflowPath: '${{ env.workflowPath}}' + - name: 'Get parameter file paths' + id: get-module-test-file-paths + uses: ./.github/actions/templates/getModuleTestFiles + with: + modulePath: '${{ env.modulePath }}' + outputs: + workflowInput: ${{ steps.get-workflow-param.outputs.workflowInput }} + moduleTestFilePaths: ${{ steps.get-module-test-file-paths.outputs.moduleTestFilePaths }} + modulePath: '${{ env.modulePath }}' + + ############################## + # Call reusable workflow # + ############################## + call-workflow-passing-data: + name: 'Module' + needs: + - job_initialize_pipeline + uses: ./.github/workflows/template.module.yml + with: + workflowInput: '${{ needs.job_initialize_pipeline.outputs.workflowInput }}' + moduleTestFilePaths: '${{ needs.job_initialize_pipeline.outputs.moduleTestFilePaths }}' + modulePath: '${{ needs.job_initialize_pipeline.outputs.modulePath}}' + secrets: inherit diff --git a/modules/relay/namespaces/.bicep/nested_roleAssignments.bicep b/modules/relay/namespaces/.bicep/nested_roleAssignments.bicep new file mode 100644 index 0000000000..fac857dc45 --- /dev/null +++ b/modules/relay/namespaces/.bicep/nested_roleAssignments.bicep @@ -0,0 +1,72 @@ +@sys.description('Required. The IDs of the principals to assign the role to.') +param principalIds array + +@sys.description('Required. The name of the role to assign. If it cannot be found you can specify the role definition ID instead.') +param roleDefinitionIdOrName string + +@sys.description('Required. The resource ID of the resource to apply the role assignment to.') +param resourceId string + +@sys.description('Optional. The principal type of the assigned principal ID.') +@allowed([ + 'ServicePrincipal' + 'Group' + 'User' + 'ForeignGroup' + 'Device' + '' +]) +param principalType string = '' + +@sys.description('Optional. The description of the role assignment.') +param description string = '' + +@sys.description('Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase "foo_storage_container".') +param condition string = '' + +@sys.description('Optional. Version of the condition.') +@allowed([ + '2.0' +]) +param conditionVersion string = '2.0' + +@sys.description('Optional. Id of the delegated managed identity resource.') +param delegatedManagedIdentityResourceId string = '' + +var builtInRoleNames = { + 'App Compliance Automation Administrator': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '0f37683f-2463-46b6-9ce7-9b788b988ba2') + 'Azure Relay Listener': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '26e0b698-aa6d-4085-9386-aadae190014d') + 'Azure Relay Owner': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '2787bf04-f1f5-4bfe-8383-c8a24483ee38') + 'Azure Relay Sender': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '26baccc8-eea7-41f1-98f4-1762cc7f685d') + Contributor: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c') + 'Log Analytics Contributor': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '92aaf0da-9dab-42b6-94a3-d43ce8d16293') + 'Log Analytics Reader': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '73c42c96-874c-492b-b04d-ab87d138a893') + 'Managed Application Contributor Role': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '641177b8-a67a-45b9-a033-47bc880bb21e') + 'Managed Application Operator Role': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'c7393b34-138c-406f-901b-d8cf2b17e6ae') + 'Managed Applications Reader': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b9331d33-8a36-4f8c-b097-4f54124fdb44') + 'Monitoring Contributor': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '749f88d5-cbae-40b8-bcfc-e573ddc772fa') + 'Monitoring Reader': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '43d0d8ad-25c7-4714-9337-8ba259a9fe05') + Owner: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635') + Reader: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7') + 'Resource Policy Contributor': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '36243c78-bf99-498c-9df9-86d9f8d28608') + 'Role Based Access Control Administrator (Preview)': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168') + 'User Access Administrator': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9') +} + +resource namespace 'Microsoft.Relay/namespaces@2021-11-01' existing = { + name: last(split(resourceId, '/'))! +} + +resource roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = [for principalId in principalIds: { + name: guid(namespace.id, principalId, roleDefinitionIdOrName) + properties: { + description: description + roleDefinitionId: contains(builtInRoleNames, roleDefinitionIdOrName) ? builtInRoleNames[roleDefinitionIdOrName] : roleDefinitionIdOrName + principalId: principalId + principalType: !empty(principalType) ? any(principalType) : null + condition: !empty(condition) ? condition : null + conditionVersion: !empty(conditionVersion) && !empty(condition) ? conditionVersion : null + delegatedManagedIdentityResourceId: !empty(delegatedManagedIdentityResourceId) ? delegatedManagedIdentityResourceId : null + } + scope: namespace +}] diff --git a/modules/relay/namespaces/.test/common/dependencies.bicep b/modules/relay/namespaces/.test/common/dependencies.bicep new file mode 100644 index 0000000000..4df36073d0 --- /dev/null +++ b/modules/relay/namespaces/.test/common/dependencies.bicep @@ -0,0 +1,60 @@ +@description('Optional. The location to deploy resources to.') +param location string = resourceGroup().location + +@description('Required. The name of the Virtual Network to create.') +param virtualNetworkName string + +@description('Required. The name of the Managed Identity to create.') +param managedIdentityName string + +var addressPrefix = '10.0.0.0/16' + +resource virtualNetwork 'Microsoft.Network/virtualNetworks@2022-01-01' = { + name: virtualNetworkName + location: location + properties: { + addressSpace: { + addressPrefixes: [ + addressPrefix + ] + } + subnets: [ + { + name: 'defaultSubnet' + properties: { + addressPrefix: addressPrefix + } + } + ] + } +} + +resource privateDNSZone 'Microsoft.Network/privateDnsZones@2020-06-01' = { + name: 'privatelink.servicebus.windows.net' + location: 'global' + + resource virtualNetworkLinks 'virtualNetworkLinks@2020-06-01' = { + name: '${virtualNetwork.name}-vnetlink' + location: 'global' + properties: { + virtualNetwork: { + id: virtualNetwork.id + } + registrationEnabled: false + } + } +} + +resource managedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2018-11-30' = { + name: managedIdentityName + location: location +} + +@description('The resource ID of the created Virtual Network Subnet.') +output subnetResourceId string = virtualNetwork.properties.subnets[0].id + +@description('The principal ID of the created Managed Identity.') +output managedIdentityPrincipalId string = managedIdentity.properties.principalId + +@description('The resource ID of the created Private DNS Zone.') +output privateDNSZoneResourceId string = privateDNSZone.id diff --git a/modules/relay/namespaces/.test/common/main.test.bicep b/modules/relay/namespaces/.test/common/main.test.bicep new file mode 100644 index 0000000000..2cf206ca7a --- /dev/null +++ b/modules/relay/namespaces/.test/common/main.test.bicep @@ -0,0 +1,169 @@ +targetScope = 'subscription' + +// ========== // +// Parameters // +// ========== // + +@description('Optional. The name of the resource group to deploy for testing purposes.') +@maxLength(90) +param resourceGroupName string = 'ms.relay.namespaces-${serviceShort}-rg' + +@description('Optional. The location to deploy resources to.') +param location string = deployment().location + +@description('Optional. A short identifier for the kind of deployment. Should be kept short to not run into resource-name length-constraints.') +param serviceShort string = 'rncom' + +@description('Optional. Enable telemetry via a Globally Unique Identifier (GUID).') +param enableDefaultTelemetry bool = true + +// ============ // +// Dependencies // +// ============ // + +// General resources +// ================= +resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = { + name: resourceGroupName + location: location +} + +module nestedDependencies 'dependencies.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, location)}-nestedDependencies' + params: { + virtualNetworkName: 'dep-<>-vnet-${serviceShort}' + managedIdentityName: 'dep-<>-msi-${serviceShort}' + } +} + +// Diagnostics +// =========== +module diagnosticDependencies '../../../../.shared/.templates/diagnostic.dependencies.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, location)}-diagnosticDependencies' + params: { + storageAccountName: 'dep<>diasa${serviceShort}01' + logAnalyticsWorkspaceName: 'dep-<>-law-${serviceShort}' + eventHubNamespaceEventHubName: 'dep-<>-evh-${serviceShort}' + eventHubNamespaceName: 'dep-<>-evhns-${serviceShort}' + location: location + } +} + +// ============== // +// Test Execution // +// ============== // + +module testDeployment '../../main.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, location)}-test-${serviceShort}' + params: { + enableDefaultTelemetry: enableDefaultTelemetry + name: '<>${serviceShort}001' + lock: 'CanNotDelete' + skuName: 'Standard' + tags: { + Environment: 'Non-Prod' + Role: 'DeploymentValidation' + } + roleAssignments: [ + { + roleDefinitionIdOrName: 'Reader' + principalIds: [ + nestedDependencies.outputs.managedIdentityPrincipalId + ] + principalType: 'ServicePrincipal' + } + ] + networkRuleSets: { + defaultAction: 'Deny' + trustedServiceAccessEnabled: true + virtualNetworkRules: [ + { + subnet: { + ignoreMissingVnetServiceEndpoint: true + id: nestedDependencies.outputs.subnetResourceId + } + } + ] + ipRules: [ + { + ipMask: '10.0.1.0/32' + action: 'Allow' + } + { + ipMask: '10.0.2.0/32' + action: 'Allow' + } + ] + } + authorizationRules: [ + { + name: 'RootManageSharedAccessKey' + rights: [ + 'Listen' + 'Manage' + 'Send' + ] + } + { + name: 'AnotherKey' + rights: [ + 'Listen' + 'Send' + ] + } + ] + hybridConnections: [ + { + name: '<>${serviceShort}hc001' + roleAssignments: [ + { + roleDefinitionIdOrName: 'Reader' + principalIds: [ + nestedDependencies.outputs.managedIdentityPrincipalId + ] + principalType: 'ServicePrincipal' + } + ] + userMetadata: '[{"key":"endpoint","value":"db-server.constoso.com:1433"}]' + } + ] + wcfRelays: [ + { + name: '<>${serviceShort}wcf001' + roleAssignments: [ + { + roleDefinitionIdOrName: 'Reader' + principalIds: [ + nestedDependencies.outputs.managedIdentityPrincipalId + ] + principalType: 'ServicePrincipal' + } + ] + relayType: 'NetTcp' + } + ] + diagnosticLogsRetentionInDays: 7 + diagnosticStorageAccountId: diagnosticDependencies.outputs.storageAccountResourceId + diagnosticWorkspaceId: diagnosticDependencies.outputs.logAnalyticsWorkspaceResourceId + diagnosticEventHubAuthorizationRuleId: diagnosticDependencies.outputs.eventHubAuthorizationRuleId + diagnosticEventHubName: diagnosticDependencies.outputs.eventHubNamespaceEventHubName + privateEndpoints: [ + { + service: 'namespace' + subnetResourceId: nestedDependencies.outputs.subnetResourceId + privateDnsZoneGroup: { + privateDNSResourceIds: [ + nestedDependencies.outputs.privateDNSZoneResourceId + ] + } + tags: { + Environment: 'Non-Prod' + Role: 'DeploymentValidation' + } + } + ] + } +} diff --git a/modules/relay/namespaces/.test/min/main.test.bicep b/modules/relay/namespaces/.test/min/main.test.bicep new file mode 100644 index 0000000000..890cb7e00c --- /dev/null +++ b/modules/relay/namespaces/.test/min/main.test.bicep @@ -0,0 +1,42 @@ +targetScope = 'subscription' + +// ========== // +// Parameters // +// ========== // + +@description('Optional. The name of the resource group to deploy for testing purposes.') +@maxLength(90) +param resourceGroupName string = 'ms.relay.namespaces-${serviceShort}-rg' + +@description('Optional. The location to deploy resources to.') +param location string = deployment().location + +@description('Optional. A short identifier for the kind of deployment. Should be kept short to not run into resource-name length-constraints.') +param serviceShort string = 'rnmin' + +@description('Optional. Enable telemetry via a Globally Unique Identifier (GUID).') +param enableDefaultTelemetry bool = true + +// ============ // +// Dependencies // +// ============ // + +// General resources +// ================= +resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = { + name: resourceGroupName + location: location +} + +// ============== // +// Test Execution // +// ============== // + +module testDeployment '../../main.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, location)}-test-${serviceShort}' + params: { + enableDefaultTelemetry: enableDefaultTelemetry + name: '<>${serviceShort}001' + } +} diff --git a/modules/relay/namespaces/.test/pe/dependencies.bicep b/modules/relay/namespaces/.test/pe/dependencies.bicep new file mode 100644 index 0000000000..e84295c592 --- /dev/null +++ b/modules/relay/namespaces/.test/pe/dependencies.bicep @@ -0,0 +1,49 @@ +@description('Optional. The location to deploy resources to.') +param location string = resourceGroup().location + +@description('Required. The name of the Virtual Network to create.') +param virtualNetworkName string + +var addressPrefix = '10.0.0.0/16' + +resource virtualNetwork 'Microsoft.Network/virtualNetworks@2022-01-01' = { + name: virtualNetworkName + location: location + properties: { + addressSpace: { + addressPrefixes: [ + addressPrefix + ] + } + subnets: [ + { + name: 'defaultSubnet' + properties: { + addressPrefix: addressPrefix + } + } + ] + } +} + +resource privateDNSZone 'Microsoft.Network/privateDnsZones@2020-06-01' = { + name: 'privatelink.servicebus.windows.net' + location: 'global' + + resource virtualNetworkLinks 'virtualNetworkLinks@2020-06-01' = { + name: '${virtualNetwork.name}-vnetlink' + location: 'global' + properties: { + virtualNetwork: { + id: virtualNetwork.id + } + registrationEnabled: false + } + } +} + +@description('The resource ID of the created Virtual Network Subnet.') +output subnetResourceId string = virtualNetwork.properties.subnets[0].id + +@description('The resource ID of the created Private DNS Zone.') +output privateDNSZoneResourceId string = privateDNSZone.id diff --git a/modules/relay/namespaces/.test/pe/main.test.bicep b/modules/relay/namespaces/.test/pe/main.test.bicep new file mode 100644 index 0000000000..c73d2eca40 --- /dev/null +++ b/modules/relay/namespaces/.test/pe/main.test.bicep @@ -0,0 +1,70 @@ +targetScope = 'subscription' + +// ========== // +// Parameters // +// ========== // + +@description('Optional. The name of the resource group to deploy for testing purposes.') +@maxLength(90) +param resourceGroupName string = 'ms.relay.namespaces-${serviceShort}-rg' + +@description('Optional. The location to deploy resources to.') +param location string = deployment().location + +@description('Optional. A short identifier for the kind of deployment. Should be kept short to not run into resource-name length-constraints.') +param serviceShort string = 'rnpe' + +@description('Optional. Enable telemetry via a Globally Unique Identifier (GUID).') +param enableDefaultTelemetry bool = true + +// ============ // +// Dependencies // +// ============ // + +// General resources +// ================= +resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = { + name: resourceGroupName + location: location +} + +module nestedDependencies 'dependencies.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, location)}-nestedDependencies' + params: { + virtualNetworkName: 'dep-<>-vnet-${serviceShort}' + } +} + +// ============== // +// Test Execution // +// ============== // + +module testDeployment '../../main.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, location)}-test-${serviceShort}' + params: { + enableDefaultTelemetry: enableDefaultTelemetry + name: '<>${serviceShort}001' + skuName: 'Standard' + privateEndpoints: [ + { + service: 'namespace' + subnetResourceId: nestedDependencies.outputs.subnetResourceId + privateDnsZoneGroup: { + privateDNSResourceIds: [ + nestedDependencies.outputs.privateDNSZoneResourceId + ] + } + tags: { + Environment: 'Non-Prod' + Role: 'DeploymentValidation' + } + } + ] + tags: { + Environment: 'Non-Prod' + Role: 'DeploymentValidation' + } + } +} diff --git a/modules/relay/namespaces/README.md b/modules/relay/namespaces/README.md new file mode 100644 index 0000000000..6bde94f50b --- /dev/null +++ b/modules/relay/namespaces/README.md @@ -0,0 +1,702 @@ +# Relay Namespaces `[Microsoft.Relay/namespaces]` + +This module deploys a Relay Namespace + +## Navigation + +- [Resource types](#Resource-types) +- [Parameters](#Parameters) +- [Outputs](#Outputs) +- [Cross-referenced modules](#Cross-referenced-modules) +- [Deployment examples](#Deployment-examples) + +## Resource types + +| Resource Type | API Version | +| :-- | :-- | +| `Microsoft.Authorization/locks` | [2020-05-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Authorization/2020-05-01/locks) | +| `Microsoft.Authorization/roleAssignments` | [2022-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Authorization/2022-04-01/roleAssignments) | +| `Microsoft.Insights/diagnosticSettings` | [2021-05-01-preview](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Insights/2021-05-01-preview/diagnosticSettings) | +| `Microsoft.Network/privateEndpoints` | [2022-07-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Network/2022-07-01/privateEndpoints) | +| `Microsoft.Network/privateEndpoints/privateDnsZoneGroups` | [2022-07-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Network/2022-07-01/privateEndpoints/privateDnsZoneGroups) | +| `Microsoft.Relay/namespaces` | [2021-11-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Relay/2021-11-01/namespaces) | +| `Microsoft.Relay/namespaces/authorizationRules` | [2021-11-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Relay/2021-11-01/namespaces/authorizationRules) | +| `Microsoft.Relay/namespaces/hybridConnections` | [2021-11-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Relay/2021-11-01/namespaces/hybridConnections) | +| `Microsoft.Relay/namespaces/hybridConnections/authorizationRules` | [2021-11-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Relay/2021-11-01/namespaces/hybridConnections/authorizationRules) | +| `Microsoft.Relay/namespaces/networkRuleSets` | [2021-11-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Relay/2021-11-01/namespaces/networkRuleSets) | +| `Microsoft.Relay/namespaces/wcfRelays` | [2021-11-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Relay/2021-11-01/namespaces/wcfRelays) | +| `Microsoft.Relay/namespaces/wcfRelays/authorizationRules` | [2021-11-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Relay/2021-11-01/namespaces/wcfRelays/authorizationRules) | + +## Parameters + +**Required parameters** + +| Parameter Name | Type | Description | +| :-- | :-- | :-- | +| `name` | string | Name of the Relay Namespace. | + +**Optional parameters** + +| Parameter Name | Type | Default Value | Allowed Values | Description | +| :-- | :-- | :-- | :-- | :-- | +| `authorizationRules` | _[authorizationRules](authorization-rules/README.md)_ array | `[System.Management.Automation.OrderedHashtable]` | | Authorization Rules for the Relay namespace. | +| `diagnosticEventHubAuthorizationRuleId` | string | `''` | | Resource ID of the diagnostic event hub authorization rule for the Event Hubs namespace in which the event hub should be created or streamed to. | +| `diagnosticEventHubName` | string | `''` | | Name of the diagnostic event hub within the namespace to which logs are streamed. Without this, an event hub is created for each log category. | +| `diagnosticLogCategoriesToEnable` | array | `[allLogs, hybridConnectionsEvent]` | `[allLogs, hybridConnectionsEvent, OperationalLogs]` | The name of logs that will be streamed. "allLogs" includes all possible logs for the resource. | +| `diagnosticLogsRetentionInDays` | int | `365` | | Specifies the number of days that logs will be kept for; a value of 0 will retain data indefinitely. | +| `diagnosticMetricsToEnable` | array | `[AllMetrics]` | `[AllMetrics]` | The name of metrics that will be streamed. | +| `diagnosticSettingsName` | string | `''` | | The name of the diagnostic setting, if deployed. If left empty, it defaults to "-diagnosticSettings". | +| `diagnosticStorageAccountId` | string | `''` | | Resource ID of the diagnostic storage account. | +| `diagnosticWorkspaceId` | string | `''` | | Resource ID of the diagnostic log analytics workspace. | +| `enableDefaultTelemetry` | bool | `True` | | Enable telemetry via a Globally Unique Identifier (GUID). | +| `hybridConnections` | _[hybridConnections](hybrid-connections/README.md)_ array | `[]` | | The hybrid connections to create in the relay namespace. | +| `location` | string | `[resourceGroup().location]` | | Location for all resources. | +| `lock` | string | `''` | `['', CanNotDelete, ReadOnly]` | Specify the type of lock. | +| `networkRuleSets` | _[networkRuleSets](network-rule-sets/README.md)_ object | `{object}` | | Configure networking options for Relay. This object contains IPs/Subnets to allow or restrict access to private endpoints only. For security reasons, it is recommended to configure this object on the Namespace. | +| `privateEndpoints` | array | `[]` | | Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible. | +| `roleAssignments` | array | `[]` | | Array of role assignment objects that contain the 'roleDefinitionIdOrName' and 'principalId' to define RBAC role assignments on this resource. In the roleDefinitionIdOrName attribute, you can provide either the display name of the role definition, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'. | +| `skuName` | string | `'Standard'` | `[Standard]` | Name of this SKU. | +| `tags` | object | `{object}` | | Tags of the resource. | +| `wcfRelays` | _[wcfRelays](wcf-relays/README.md)_ array | `[]` | | The wcf relays to create in the relay namespace. | + + +### Parameter Usage: `roleAssignments` + +Create a role assignment for the given resource. If you want to assign a service principal / managed identity that is created in the same deployment, make sure to also specify the `'principalType'` parameter and set it to `'ServicePrincipal'`. This will ensure the role assignment waits for the principal's propagation in Azure. + +
+ +Parameter JSON format + +```json +"roleAssignments": { + "value": [ + { + "roleDefinitionIdOrName": "Reader", + "description": "Reader Role Assignment", + "principalIds": [ + "12345678-1234-1234-1234-123456789012", // object 1 + "78945612-1234-1234-1234-123456789012" // object 2 + ] + }, + { + "roleDefinitionIdOrName": "/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11", + "principalIds": [ + "12345678-1234-1234-1234-123456789012" // object 1 + ], + "principalType": "ServicePrincipal" + } + ] +} +``` + +
+ +
+ +Bicep format + +```bicep +roleAssignments: [ + { + roleDefinitionIdOrName: 'Reader' + description: 'Reader Role Assignment' + principalIds: [ + '12345678-1234-1234-1234-123456789012' // object 1 + '78945612-1234-1234-1234-123456789012' // object 2 + ] + } + { + roleDefinitionIdOrName: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11' + principalIds: [ + '12345678-1234-1234-1234-123456789012' // object 1 + ] + principalType: 'ServicePrincipal' + } +] +``` + +
+

+ +### Parameter Usage: `privateEndpoints` + +To use Private Endpoint the following dependencies must be deployed: + +- Destination subnet must be created with the following configuration option - `"privateEndpointNetworkPolicies": "Disabled"`. Setting this option acknowledges that NSG rules are not applied to Private Endpoints (this capability is coming soon). A full example is available in the Virtual Network Module. +- Although not strictly required, it is highly recommended to first create a private DNS Zone to host Private Endpoint DNS records. See [Azure Private Endpoint DNS configuration](https://learn.microsoft.com/en-us/azure/private-link/private-endpoint-dns) for more information. + +

+ +Parameter JSON format + +```json +"privateEndpoints": { + "value": [ + // Example showing all available fields + { + "name": "sxx-az-pe", // Optional: Name will be automatically generated if one is not provided here + "subnetResourceId": "/subscriptions/<>/resourceGroups/validation-rg/providers/Microsoft.Network/virtualNetworks/sxx-az-vnet-x-001/subnets/sxx-az-subnet-x-001", + "service": "", // e.g. vault, registry, blob + "privateDnsZoneGroup": { + "privateDNSResourceIds": [ // Optional: No DNS record will be created if a private DNS zone Resource ID is not specified + "/subscriptions/<>/resourceGroups/validation-rg/providers/Microsoft.Network/privateDnsZones/" // e.g. privatelink.vaultcore.azure.net, privatelink.azurecr.io, privatelink.blob.core.windows.net + ] + }, + "ipConfigurations":[ + { + "name": "myIPconfigTest02", + "properties": { + "groupId": "blob", + "memberName": "blob", + "privateIPAddress": "10.0.0.30" + } + } + ], + "customDnsConfigs": [ + { + "fqdn": "customname.test.local", + "ipAddresses": [ + "10.10.10.10" + ] + } + ] + }, + // Example showing only mandatory fields + { + "subnetResourceId": "/subscriptions/<>/resourceGroups/validation-rg/providers/Microsoft.Network/virtualNetworks/sxx-az-vnet-x-001/subnets/sxx-az-subnet-x-001", + "service": "" // e.g. vault, registry, blob + } + ] +} +``` + +
+ +
+ +Bicep format + +```bicep +privateEndpoints: [ + // Example showing all available fields + { + name: 'sxx-az-pe' // Optional: Name will be automatically generated if one is not provided here + subnetResourceId: '/subscriptions/<>/resourceGroups/validation-rg/providers/Microsoft.Network/virtualNetworks/sxx-az-vnet-x-001/subnets/sxx-az-subnet-x-001' + service: '' // e.g. vault, registry, blob + privateDnsZoneGroup: { + privateDNSResourceIds: [ // Optional: No DNS record will be created if a private DNS zone Resource ID is not specified + '/subscriptions/<>/resourceGroups/validation-rg/providers/Microsoft.Network/privateDnsZones/' // e.g. privatelink.vaultcore.azure.net, privatelink.azurecr.io, privatelink.blob.core.windows.net + ] + } + customDnsConfigs: [ + { + fqdn: 'customname.test.local' + ipAddresses: [ + '10.10.10.10' + ] + } + ] + ipConfigurations:[ + { + name: 'myIPconfigTest02' + properties: { + groupId: 'blob' + memberName: 'blob' + privateIPAddress: '10.0.0.30' + } + } + ] + } + // Example showing only mandatory fields + { + subnetResourceId: '/subscriptions/<>/resourceGroups/validation-rg/providers/Microsoft.Network/virtualNetworks/sxx-az-vnet-x-001/subnets/sxx-az-subnet-x-001' + service: '' // e.g. vault, registry, blob + } +] +``` + +
+

+ +### Parameter Usage: `tags` + +Tag names and tag values can be provided as needed. A tag can be left without a value. + +

+ +Parameter JSON format + +```json +"tags": { + "value": { + "Environment": "Non-Prod", + "Contact": "test.user@testcompany.com", + "PurchaseOrder": "1234", + "CostCenter": "7890", + "ServiceName": "DeploymentValidation", + "Role": "DeploymentValidation" + } +} +``` + +
+ +
+ +Bicep format + +```bicep +tags: { + Environment: 'Non-Prod' + Contact: 'test.user@testcompany.com' + PurchaseOrder: '1234' + CostCenter: '7890' + ServiceName: 'DeploymentValidation' + Role: 'DeploymentValidation' +} +``` + +
+

+ +## Outputs + +| Output Name | Type | Description | +| :-- | :-- | :-- | +| `location` | string | The location the resource was deployed into. | +| `name` | string | The name of the deployed relay namespace. | +| `resourceGroupName` | string | The resource group of the deployed relay namespace. | +| `resourceId` | string | The resource ID of the deployed relay namespace. | + +## Cross-referenced modules + +This section gives you an overview of all local-referenced module files (i.e., other CARML modules that are referenced in this module) and all remote-referenced files (i.e., Bicep modules that are referenced from a Bicep Registry or Template Specs). + +| Reference | Type | +| :-- | :-- | +| `network/private-endpoints` | Local reference | + +## Deployment examples + +The following module usage examples are retrieved from the content of the files hosted in the module's `.test` folder. + >**Note**: The name of each example is based on the name of the file from which it is taken. + + >**Note**: Each example lists all the required parameters first, followed by the rest - each in alphabetical order. + +

Example 1: Common

+ +
+ +via Bicep module + +```bicep +module namespaces './relay/namespaces/main.bicep' = { + name: '${uniqueString(deployment().name, location)}-test-rncom' + params: { + // Required parameters + name: '<>rncom001' + // Non-required parameters + authorizationRules: [ + { + name: 'RootManageSharedAccessKey' + rights: [ + 'Listen' + 'Manage' + 'Send' + ] + } + { + name: 'AnotherKey' + rights: [ + 'Listen' + 'Send' + ] + } + ] + diagnosticEventHubAuthorizationRuleId: '' + diagnosticEventHubName: '' + diagnosticLogsRetentionInDays: 7 + diagnosticStorageAccountId: '' + diagnosticWorkspaceId: '' + enableDefaultTelemetry: '' + hybridConnections: [ + { + name: '<>rncomhc001' + roleAssignments: [ + { + principalIds: [ + '' + ] + principalType: 'ServicePrincipal' + roleDefinitionIdOrName: 'Reader' + } + ] + userMetadata: '[{\'key\':\'endpoint\'\'value\':\'db-server.constoso.com:1433\'}]' + } + ] + lock: 'CanNotDelete' + networkRuleSets: { + defaultAction: 'Deny' + ipRules: [ + { + action: 'Allow' + ipMask: '10.0.1.0/32' + } + { + action: 'Allow' + ipMask: '10.0.2.0/32' + } + ] + trustedServiceAccessEnabled: true + virtualNetworkRules: [ + { + subnet: { + id: '' + ignoreMissingVnetServiceEndpoint: true + } + } + ] + } + privateEndpoints: [ + { + privateDnsZoneGroup: { + privateDNSResourceIds: [ + '' + ] + } + service: 'namespace' + subnetResourceId: '' + tags: { + Environment: 'Non-Prod' + Role: 'DeploymentValidation' + } + } + ] + roleAssignments: [ + { + principalIds: [ + '' + ] + principalType: 'ServicePrincipal' + roleDefinitionIdOrName: 'Reader' + } + ] + skuName: 'Standard' + tags: { + Environment: 'Non-Prod' + Role: 'DeploymentValidation' + } + wcfRelays: [ + { + name: '<>rncomwcf001' + relayType: 'NetTcp' + roleAssignments: [ + { + principalIds: [ + '' + ] + principalType: 'ServicePrincipal' + roleDefinitionIdOrName: 'Reader' + } + ] + } + ] + } +} +``` + +
+

+ +

+ +via JSON Parameter file + +```json +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + // Required parameters + "name": { + "value": "<>rncom001" + }, + // Non-required parameters + "authorizationRules": { + "value": [ + { + "name": "RootManageSharedAccessKey", + "rights": [ + "Listen", + "Manage", + "Send" + ] + }, + { + "name": "AnotherKey", + "rights": [ + "Listen", + "Send" + ] + } + ] + }, + "diagnosticEventHubAuthorizationRuleId": { + "value": "" + }, + "diagnosticEventHubName": { + "value": "" + }, + "diagnosticLogsRetentionInDays": { + "value": 7 + }, + "diagnosticStorageAccountId": { + "value": "" + }, + "diagnosticWorkspaceId": { + "value": "" + }, + "enableDefaultTelemetry": { + "value": "" + }, + "hybridConnections": { + "value": [ + { + "name": "<>rncomhc001", + "roleAssignments": [ + { + "principalIds": [ + "" + ], + "principalType": "ServicePrincipal", + "roleDefinitionIdOrName": "Reader" + } + ], + "userMetadata": "[{\"key\":\"endpoint\",\"value\":\"db-server.constoso.com:1433\"}]" + } + ] + }, + "lock": { + "value": "CanNotDelete" + }, + "networkRuleSets": { + "value": { + "defaultAction": "Deny", + "ipRules": [ + { + "action": "Allow", + "ipMask": "10.0.1.0/32" + }, + { + "action": "Allow", + "ipMask": "10.0.2.0/32" + } + ], + "trustedServiceAccessEnabled": true, + "virtualNetworkRules": [ + { + "subnet": { + "id": "", + "ignoreMissingVnetServiceEndpoint": true + } + } + ] + } + }, + "privateEndpoints": { + "value": [ + { + "privateDnsZoneGroup": { + "privateDNSResourceIds": [ + "" + ] + }, + "service": "namespace", + "subnetResourceId": "", + "tags": { + "Environment": "Non-Prod", + "Role": "DeploymentValidation" + } + } + ] + }, + "roleAssignments": { + "value": [ + { + "principalIds": [ + "" + ], + "principalType": "ServicePrincipal", + "roleDefinitionIdOrName": "Reader" + } + ] + }, + "skuName": { + "value": "Standard" + }, + "tags": { + "value": { + "Environment": "Non-Prod", + "Role": "DeploymentValidation" + } + }, + "wcfRelays": { + "value": [ + { + "name": "<>rncomwcf001", + "relayType": "NetTcp", + "roleAssignments": [ + { + "principalIds": [ + "" + ], + "principalType": "ServicePrincipal", + "roleDefinitionIdOrName": "Reader" + } + ] + } + ] + } + } +} +``` + +
+

+ +

Example 2: Min

+ +
+ +via Bicep module + +```bicep +module namespaces './relay/namespaces/main.bicep' = { + name: '${uniqueString(deployment().name, location)}-test-rnmin' + params: { + // Required parameters + name: '<>rnmin001' + // Non-required parameters + enableDefaultTelemetry: '' + } +} +``` + +
+

+ +

+ +via JSON Parameter file + +```json +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + // Required parameters + "name": { + "value": "<>rnmin001" + }, + // Non-required parameters + "enableDefaultTelemetry": { + "value": "" + } + } +} +``` + +
+

+ +

Example 3: Pe

+ +
+ +via Bicep module + +```bicep +module namespaces './relay/namespaces/main.bicep' = { + name: '${uniqueString(deployment().name, location)}-test-rnpe' + params: { + // Required parameters + name: '<>rnpe001' + // Non-required parameters + enableDefaultTelemetry: '' + privateEndpoints: [ + { + privateDnsZoneGroup: { + privateDNSResourceIds: [ + '' + ] + } + service: 'namespace' + subnetResourceId: '' + tags: { + Environment: 'Non-Prod' + Role: 'DeploymentValidation' + } + } + ] + skuName: 'Standard' + tags: { + Environment: 'Non-Prod' + Role: 'DeploymentValidation' + } + } +} +``` + +
+

+ +

+ +via JSON Parameter file + +```json +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + // Required parameters + "name": { + "value": "<>rnpe001" + }, + // Non-required parameters + "enableDefaultTelemetry": { + "value": "" + }, + "privateEndpoints": { + "value": [ + { + "privateDnsZoneGroup": { + "privateDNSResourceIds": [ + "" + ] + }, + "service": "namespace", + "subnetResourceId": "", + "tags": { + "Environment": "Non-Prod", + "Role": "DeploymentValidation" + } + } + ] + }, + "skuName": { + "value": "Standard" + }, + "tags": { + "value": { + "Environment": "Non-Prod", + "Role": "DeploymentValidation" + } + } + } +} +``` + +
+

diff --git a/modules/relay/namespaces/authorization-rules/README.md b/modules/relay/namespaces/authorization-rules/README.md new file mode 100644 index 0000000000..44420982c3 --- /dev/null +++ b/modules/relay/namespaces/authorization-rules/README.md @@ -0,0 +1,50 @@ +# Relay Namespace Authorization Rules `[Microsoft.Relay/namespaces/authorizationRules]` + +This module deploys a Relay Namespace Authorization Rule. + +## Navigation + +- [Resource Types](#Resource-Types) +- [Parameters](#Parameters) +- [Outputs](#Outputs) +- [Cross-referenced modules](#Cross-referenced-modules) + +## Resource Types + +| Resource Type | API Version | +| :-- | :-- | +| `Microsoft.Relay/namespaces/authorizationRules` | [2021-11-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Relay/2021-11-01/namespaces/authorizationRules) | + +## Parameters + +**Required parameters** + +| Parameter Name | Type | Description | +| :-- | :-- | :-- | +| `name` | string | The name of the authorization rule. | + +**Conditional parameters** + +| Parameter Name | Type | Description | +| :-- | :-- | :-- | +| `namespaceName` | string | The name of the parent Relay Namespace for the Relay Hybrid Connection. Required if the template is used in a standalone deployment. | + +**Optional parameters** + +| Parameter Name | Type | Default Value | Allowed Values | Description | +| :-- | :-- | :-- | :-- | :-- | +| `enableDefaultTelemetry` | bool | `True` | | Enable telemetry via a Globally Unique Identifier (GUID). | +| `rights` | array | `[]` | `[Listen, Manage, Send]` | The rights associated with the rule. | + + +## Outputs + +| Output Name | Type | Description | +| :-- | :-- | :-- | +| `name` | string | The name of the authorization rule. | +| `resourceGroupName` | string | The name of the Resource Group the authorization rule was created in. | +| `resourceId` | string | The resource ID of the authorization rule. | + +## Cross-referenced modules + +_None_ diff --git a/modules/relay/namespaces/authorization-rules/main.bicep b/modules/relay/namespaces/authorization-rules/main.bicep new file mode 100644 index 0000000000..e7ce609c74 --- /dev/null +++ b/modules/relay/namespaces/authorization-rules/main.bicep @@ -0,0 +1,51 @@ +@description('Conditional. The name of the parent Relay Namespace for the Relay Hybrid Connection. Required if the template is used in a standalone deployment.') +@minLength(6) +@maxLength(50) +param namespaceName string + +@description('Required. The name of the authorization rule.') +param name string + +@description('Optional. The rights associated with the rule.') +@allowed([ + 'Listen' + 'Manage' + 'Send' +]) +param rights array = [] + +@description('Optional. Enable telemetry via a Globally Unique Identifier (GUID).') +param enableDefaultTelemetry bool = true + +resource defaultTelemetry 'Microsoft.Resources/deployments@2021-04-01' = if (enableDefaultTelemetry) { + name: 'pid-47ed15a6-730a-4827-bcb4-0fd963ffbd82-${uniqueString(deployment().name)}' + properties: { + mode: 'Incremental' + template: { + '$schema': 'https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#' + contentVersion: '1.0.0.0' + resources: [] + } + } +} + +resource namespace 'Microsoft.Relay/namespaces@2021-11-01' existing = { + name: namespaceName +} + +resource authorizationRule 'Microsoft.Relay/namespaces/authorizationRules@2021-11-01' = { + name: name + parent: namespace + properties: { + rights: rights + } +} + +@description('The name of the authorization rule.') +output name string = authorizationRule.name + +@description('The resource ID of the authorization rule.') +output resourceId string = authorizationRule.id + +@description('The name of the Resource Group the authorization rule was created in.') +output resourceGroupName string = resourceGroup().name diff --git a/modules/relay/namespaces/authorization-rules/metadata.json b/modules/relay/namespaces/authorization-rules/metadata.json new file mode 100644 index 0000000000..272f91d0a9 --- /dev/null +++ b/modules/relay/namespaces/authorization-rules/metadata.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://aka.ms/bicep-registry-module-metadata-file-schema-v2#", + "name": "Relay Namespace Authorization Rules", + "summary": "This module deploys a Relay Namespace Authorization Rule.", + "owner": "Azure/module-maintainers" +} diff --git a/modules/relay/namespaces/authorization-rules/version.json b/modules/relay/namespaces/authorization-rules/version.json new file mode 100644 index 0000000000..5b9f717b34 --- /dev/null +++ b/modules/relay/namespaces/authorization-rules/version.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://aka.ms/bicep-registry-module-version-file-schema#", + "version": "0.4", + "pathFilters": [ + "./main.json", + "./metadata.json" + ] +} diff --git a/modules/relay/namespaces/hybrid-connections/.bicep/nested_roleAssignments.bicep b/modules/relay/namespaces/hybrid-connections/.bicep/nested_roleAssignments.bicep new file mode 100644 index 0000000000..95709ae56f --- /dev/null +++ b/modules/relay/namespaces/hybrid-connections/.bicep/nested_roleAssignments.bicep @@ -0,0 +1,72 @@ +@sys.description('Required. The IDs of the principals to assign the role to.') +param principalIds array + +@sys.description('Required. The name of the role to assign. If it cannot be found you can specify the role definition ID instead.') +param roleDefinitionIdOrName string + +@sys.description('Required. The resource ID of the resource to apply the role assignment to.') +param resourceId string + +@sys.description('Optional. The principal type of the assigned principal ID.') +@allowed([ + 'ServicePrincipal' + 'Group' + 'User' + 'ForeignGroup' + 'Device' + '' +]) +param principalType string = '' + +@sys.description('Optional. The description of the role assignment.') +param description string = '' + +@sys.description('Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase "foo_storage_container".') +param condition string = '' + +@sys.description('Optional. Version of the condition.') +@allowed([ + '2.0' +]) +param conditionVersion string = '2.0' + +@sys.description('Optional. Id of the delegated managed identity resource.') +param delegatedManagedIdentityResourceId string = '' + +var builtInRoleNames = { + 'App Compliance Automation Administrator': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '0f37683f-2463-46b6-9ce7-9b788b988ba2') + 'Azure Relay Listener': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '26e0b698-aa6d-4085-9386-aadae190014d') + 'Azure Relay Owner': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '2787bf04-f1f5-4bfe-8383-c8a24483ee38') + 'Azure Relay Sender': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '26baccc8-eea7-41f1-98f4-1762cc7f685d') + Contributor: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c') + 'Log Analytics Contributor': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '92aaf0da-9dab-42b6-94a3-d43ce8d16293') + 'Log Analytics Reader': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '73c42c96-874c-492b-b04d-ab87d138a893') + 'Managed Application Contributor Role': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '641177b8-a67a-45b9-a033-47bc880bb21e') + 'Managed Application Operator Role': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'c7393b34-138c-406f-901b-d8cf2b17e6ae') + 'Managed Applications Reader': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b9331d33-8a36-4f8c-b097-4f54124fdb44') + 'Monitoring Contributor': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '749f88d5-cbae-40b8-bcfc-e573ddc772fa') + 'Monitoring Reader': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '43d0d8ad-25c7-4714-9337-8ba259a9fe05') + Owner: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635') + Reader: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7') + 'Resource Policy Contributor': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '36243c78-bf99-498c-9df9-86d9f8d28608') + 'Role Based Access Control Administrator (Preview)': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168') + 'User Access Administrator': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9') +} + +resource hybridConnection 'Microsoft.Relay/namespaces/hybridConnections@2021-11-01' existing = { + name: '${split(resourceId, '/')[8]}/${split(resourceId, '/')[10]}' +} + +resource roleAssigment 'Microsoft.Authorization/roleAssignments@2022-04-01' = [for principalId in principalIds: { + name: guid(hybridConnection.id, principalId, roleDefinitionIdOrName) + properties: { + description: description + roleDefinitionId: contains(builtInRoleNames, roleDefinitionIdOrName) ? builtInRoleNames[roleDefinitionIdOrName] : roleDefinitionIdOrName + principalId: principalId + principalType: !empty(principalType) ? any(principalType) : null + condition: !empty(condition) ? condition : null + conditionVersion: !empty(conditionVersion) && !empty(condition) ? conditionVersion : null + delegatedManagedIdentityResourceId: !empty(delegatedManagedIdentityResourceId) ? delegatedManagedIdentityResourceId : null + } + scope: hybridConnection +}] diff --git a/modules/relay/namespaces/hybrid-connections/README.md b/modules/relay/namespaces/hybrid-connections/README.md new file mode 100644 index 0000000000..6b0c0fd16d --- /dev/null +++ b/modules/relay/namespaces/hybrid-connections/README.md @@ -0,0 +1,116 @@ +# Relay Namespace Hybrid Connections `[Microsoft.Relay/namespaces/hybridConnections]` + +This module deploys a Relay Namespace Hybrid Connection. + +## Navigation + +- [Resource types](#Resource-types) +- [Parameters](#Parameters) +- [Outputs](#Outputs) +- [Cross-referenced modules](#Cross-referenced-modules) + +## Resource types + +| Resource Type | API Version | +| :-- | :-- | +| `Microsoft.Authorization/locks` | [2020-05-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Authorization/2020-05-01/locks) | +| `Microsoft.Authorization/roleAssignments` | [2022-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Authorization/2022-04-01/roleAssignments) | +| `Microsoft.Relay/namespaces/hybridConnections` | [2021-11-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Relay/2021-11-01/namespaces/hybridConnections) | +| `Microsoft.Relay/namespaces/hybridConnections/authorizationRules` | [2021-11-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Relay/2021-11-01/namespaces/hybridConnections/authorizationRules) | + +## Parameters + +**Required parameters** + +| Parameter Name | Type | Description | +| :-- | :-- | :-- | +| `name` | string | The name of the hybrid connection. | +| `userMetadata` | string | The user metadata is a placeholder to store user-defined string data for the hybrid connection endpoint. For example, it can be used to store descriptive data, such as a list of teams and their contact information. Also, user-defined configuration settings can be stored. | + +**Conditional parameters** + +| Parameter Name | Type | Description | +| :-- | :-- | :-- | +| `namespaceName` | string | The name of the parent Relay Namespace for the Relay Hybrid Connection. Required if the template is used in a standalone deployment. | + +**Optional parameters** + +| Parameter Name | Type | Default Value | Allowed Values | Description | +| :-- | :-- | :-- | :-- | :-- | +| `authorizationRules` | _[authorizationRules](authorization-rules/README.md)_ array | `[System.Management.Automation.OrderedHashtable, System.Management.Automation.OrderedHashtable, System.Management.Automation.OrderedHashtable]` | | Authorization Rules for the Relay Hybrid Connection. | +| `enableDefaultTelemetry` | bool | `True` | | Enable telemetry via a Globally Unique Identifier (GUID). | +| `lock` | string | `''` | `['', CanNotDelete, ReadOnly]` | Specify the type of lock. | +| `requiresClientAuthorization` | bool | `True` | | A value indicating if this hybrid connection requires client authorization. | +| `roleAssignments` | array | `[]` | | Array of role assignment objects that contain the 'roleDefinitionIdOrName' and 'principalId' to define RBAC role assignments on this resource. In the roleDefinitionIdOrName attribute, you can provide either the display name of the role definition, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'. | + + +### Parameter Usage: `roleAssignments` + +Create a role assignment for the given resource. If you want to assign a service principal / managed identity that is created in the same deployment, make sure to also specify the `'principalType'` parameter and set it to `'ServicePrincipal'`. This will ensure the role assignment waits for the principal's propagation in Azure. + +

+ +Parameter JSON format + +```json +"roleAssignments": { + "value": [ + { + "roleDefinitionIdOrName": "Reader", + "description": "Reader Role Assignment", + "principalIds": [ + "12345678-1234-1234-1234-123456789012", // object 1 + "78945612-1234-1234-1234-123456789012" // object 2 + ] + }, + { + "roleDefinitionIdOrName": "/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11", + "principalIds": [ + "12345678-1234-1234-1234-123456789012" // object 1 + ], + "principalType": "ServicePrincipal" + } + ] +} +``` + +
+ +
+ +Bicep format + +```bicep +roleAssignments: [ + { + roleDefinitionIdOrName: 'Reader' + description: 'Reader Role Assignment' + principalIds: [ + '12345678-1234-1234-1234-123456789012' // object 1 + '78945612-1234-1234-1234-123456789012' // object 2 + ] + } + { + roleDefinitionIdOrName: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11' + principalIds: [ + '12345678-1234-1234-1234-123456789012' // object 1 + ] + principalType: 'ServicePrincipal' + } +] +``` + +
+

+ +## Outputs + +| Output Name | Type | Description | +| :-- | :-- | :-- | +| `name` | string | The name of the deployed hybrid connection. | +| `resourceGroupName` | string | The resource group of the deployed hybrid connection. | +| `resourceId` | string | The resource ID of the deployed hybrid connection. | + +## Cross-referenced modules + +_None_ diff --git a/modules/relay/namespaces/hybrid-connections/authorization-rules/README.md b/modules/relay/namespaces/hybrid-connections/authorization-rules/README.md new file mode 100644 index 0000000000..cd97c6739b --- /dev/null +++ b/modules/relay/namespaces/hybrid-connections/authorization-rules/README.md @@ -0,0 +1,51 @@ +# Hybrid Connection Authorization Rules `[Microsoft.Relay/namespaces/hybridConnections/authorizationRules]` + +This module deploys a Hybrid Connection Authorization Rule. + +## Navigation + +- [Resource Types](#Resource-Types) +- [Parameters](#Parameters) +- [Outputs](#Outputs) +- [Cross-referenced modules](#Cross-referenced-modules) + +## Resource Types + +| Resource Type | API Version | +| :-- | :-- | +| `Microsoft.Relay/namespaces/hybridConnections/authorizationRules` | [2021-11-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Relay/2021-11-01/namespaces/hybridConnections/authorizationRules) | + +## Parameters + +**Required parameters** + +| Parameter Name | Type | Description | +| :-- | :-- | :-- | +| `name` | string | The name of the authorization rule. | + +**Conditional parameters** + +| Parameter Name | Type | Description | +| :-- | :-- | :-- | +| `hybridConnectionName` | string | The name of the parent Relay Namespace Hybrid Connection. Required if the template is used in a standalone deployment. | +| `namespaceName` | string | The name of the parent Relay Namespace. Required if the template is used in a standalone deployment. | + +**Optional parameters** + +| Parameter Name | Type | Default Value | Allowed Values | Description | +| :-- | :-- | :-- | :-- | :-- | +| `enableDefaultTelemetry` | bool | `True` | | Enable telemetry via a Globally Unique Identifier (GUID). | +| `rights` | array | `[]` | `[Listen, Manage, Send]` | The rights associated with the rule. | + + +## Outputs + +| Output Name | Type | Description | +| :-- | :-- | :-- | +| `name` | string | The name of the authorization rule. | +| `resourceGroupName` | string | The name of the Resource Group the authorization rule was created in. | +| `resourceId` | string | The Resource ID of the authorization rule. | + +## Cross-referenced modules + +_None_ diff --git a/modules/relay/namespaces/hybrid-connections/authorization-rules/main.bicep b/modules/relay/namespaces/hybrid-connections/authorization-rules/main.bicep new file mode 100644 index 0000000000..9adf2bb9da --- /dev/null +++ b/modules/relay/namespaces/hybrid-connections/authorization-rules/main.bicep @@ -0,0 +1,56 @@ +@description('Required. The name of the authorization rule.') +param name string + +@description('Conditional. The name of the parent Relay Namespace. Required if the template is used in a standalone deployment.') +param namespaceName string + +@description('Conditional. The name of the parent Relay Namespace Hybrid Connection. Required if the template is used in a standalone deployment.') +param hybridConnectionName string + +@description('Optional. The rights associated with the rule.') +@allowed([ + 'Listen' + 'Manage' + 'Send' +]) +param rights array = [] + +@description('Optional. Enable telemetry via a Globally Unique Identifier (GUID).') +param enableDefaultTelemetry bool = true + +resource defaultTelemetry 'Microsoft.Resources/deployments@2021-04-01' = if (enableDefaultTelemetry) { + name: 'pid-47ed15a6-730a-4827-bcb4-0fd963ffbd82-${uniqueString(deployment().name)}' + properties: { + mode: 'Incremental' + template: { + '$schema': 'https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#' + contentVersion: '1.0.0.0' + resources: [] + } + } +} + +resource namespace 'Microsoft.Relay/namespaces@2021-11-01' existing = { + name: namespaceName + + resource hybridConnection 'hybridConnections@2021-11-01' existing = { + name: hybridConnectionName + } +} + +resource authorizationRule 'Microsoft.Relay/namespaces/hybridConnections/authorizationRules@2021-11-01' = { + name: name + parent: namespace::hybridConnection + properties: { + rights: rights + } +} + +@description('The name of the authorization rule.') +output name string = authorizationRule.name + +@description('The Resource ID of the authorization rule.') +output resourceId string = authorizationRule.id + +@description('The name of the Resource Group the authorization rule was created in.') +output resourceGroupName string = resourceGroup().name diff --git a/modules/relay/namespaces/hybrid-connections/authorization-rules/metadata.json b/modules/relay/namespaces/hybrid-connections/authorization-rules/metadata.json new file mode 100644 index 0000000000..03bcef7d7a --- /dev/null +++ b/modules/relay/namespaces/hybrid-connections/authorization-rules/metadata.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://aka.ms/bicep-registry-module-metadata-file-schema-v2#", + "name": "Hybrid Connection Authorization Rules", + "summary": "This module deploys a Hybrid Connection Authorization Rule.", + "owner": "Azure/module-maintainers" +} diff --git a/modules/relay/namespaces/hybrid-connections/authorization-rules/version.json b/modules/relay/namespaces/hybrid-connections/authorization-rules/version.json new file mode 100644 index 0000000000..5b9f717b34 --- /dev/null +++ b/modules/relay/namespaces/hybrid-connections/authorization-rules/version.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://aka.ms/bicep-registry-module-version-file-schema#", + "version": "0.4", + "pathFilters": [ + "./main.json", + "./metadata.json" + ] +} diff --git a/modules/relay/namespaces/hybrid-connections/main.bicep b/modules/relay/namespaces/hybrid-connections/main.bicep new file mode 100644 index 0000000000..1affdbb154 --- /dev/null +++ b/modules/relay/namespaces/hybrid-connections/main.bicep @@ -0,0 +1,122 @@ +@description('Conditional. The name of the parent Relay Namespace for the Relay Hybrid Connection. Required if the template is used in a standalone deployment.') +@minLength(6) +@maxLength(50) +param namespaceName string + +@description('Required. The name of the hybrid connection.') +@minLength(6) +@maxLength(50) +param name string + +@description('Required. The user metadata is a placeholder to store user-defined string data for the hybrid connection endpoint. For example, it can be used to store descriptive data, such as a list of teams and their contact information. Also, user-defined configuration settings can be stored.') +param userMetadata string + +@description('Optional. A value indicating if this hybrid connection requires client authorization.') +param requiresClientAuthorization bool = true + +@description('Optional. Authorization Rules for the Relay Hybrid Connection.') +param authorizationRules array = [ + { + name: 'RootManageSharedAccessKey' + rights: [ + 'Listen' + 'Manage' + 'Send' + ] + } + { + name: 'defaultListener' + rights: [ + 'Listen' + ] + } + { + name: 'defaultSender' + rights: [ + 'Send' + ] + } +] + +@allowed([ + '' + 'CanNotDelete' + 'ReadOnly' +]) +@description('Optional. Specify the type of lock.') +param lock string = '' + +@description('Optional. Array of role assignment objects that contain the \'roleDefinitionIdOrName\' and \'principalId\' to define RBAC role assignments on this resource. In the roleDefinitionIdOrName attribute, you can provide either the display name of the role definition, or its fully qualified ID in the following format: \'/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11\'.') +param roleAssignments array = [] + +@description('Optional. Enable telemetry via a Globally Unique Identifier (GUID).') +param enableDefaultTelemetry bool = true + +var enableReferencedModulesTelemetry = false + +resource defaultTelemetry 'Microsoft.Resources/deployments@2021-04-01' = if (enableDefaultTelemetry) { + name: 'pid-47ed15a6-730a-4827-bcb4-0fd963ffbd82-${uniqueString(deployment().name)}' + properties: { + mode: 'Incremental' + template: { + '$schema': 'https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#' + contentVersion: '1.0.0.0' + resources: [] + } + } +} + +resource namespace 'Microsoft.Relay/namespaces@2021-11-01' existing = { + name: namespaceName +} + +resource hybridConnection 'Microsoft.Relay/namespaces/hybridConnections@2021-11-01' = { + name: name + parent: namespace + properties: { + requiresClientAuthorization: requiresClientAuthorization + userMetadata: userMetadata + } +} + +module hybridConnection_authorizationRules 'authorization-rules/main.bicep' = [for (authorizationRule, index) in authorizationRules: { + name: '${deployment().name}-AuthorizationRule-${index}' + params: { + namespaceName: namespaceName + hybridConnectionName: hybridConnection.name + name: authorizationRule.name + rights: contains(authorizationRule, 'rights') ? authorizationRule.rights : [] + enableDefaultTelemetry: enableReferencedModulesTelemetry + } +}] + +resource hybridConnection_lock 'Microsoft.Authorization/locks@2020-05-01' = if (!empty(lock)) { + name: '${hybridConnection.name}-${lock}-lock' + properties: { + level: any(lock) + notes: lock == 'CanNotDelete' ? 'Cannot delete resource or child resources.' : 'Cannot modify the resource or child resources.' + } + scope: hybridConnection +} + +module hybridConnection_roleAssignments '.bicep/nested_roleAssignments.bicep' = [for (roleAssignment, index) in roleAssignments: { + name: '${deployment().name}-Rbac-${index}' + params: { + description: contains(roleAssignment, 'description') ? roleAssignment.description : '' + principalIds: roleAssignment.principalIds + principalType: contains(roleAssignment, 'principalType') ? roleAssignment.principalType : '' + roleDefinitionIdOrName: roleAssignment.roleDefinitionIdOrName + condition: contains(roleAssignment, 'condition') ? roleAssignment.condition : '' + delegatedManagedIdentityResourceId: contains(roleAssignment, 'delegatedManagedIdentityResourceId') ? roleAssignment.delegatedManagedIdentityResourceId : '' + resourceId: hybridConnection.id + } +}] + +@description('The name of the deployed hybrid connection.') +output name string = hybridConnection.name + +@description('The resource ID of the deployed hybrid connection.') +output resourceId string = hybridConnection.id + +@description('The resource group of the deployed hybrid connection.') +output resourceGroupName string = resourceGroup().name diff --git a/modules/relay/namespaces/hybrid-connections/metadata.json b/modules/relay/namespaces/hybrid-connections/metadata.json new file mode 100644 index 0000000000..f1c11cafe2 --- /dev/null +++ b/modules/relay/namespaces/hybrid-connections/metadata.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://aka.ms/bicep-registry-module-metadata-file-schema-v2#", + "name": "Relay Namespace Hybrid Connections", + "summary": "This module deploys a Relay Namespace Hybrid Connection.", + "owner": "Azure/module-maintainers" +} diff --git a/modules/relay/namespaces/hybrid-connections/version.json b/modules/relay/namespaces/hybrid-connections/version.json new file mode 100644 index 0000000000..5b9f717b34 --- /dev/null +++ b/modules/relay/namespaces/hybrid-connections/version.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://aka.ms/bicep-registry-module-version-file-schema#", + "version": "0.4", + "pathFilters": [ + "./main.json", + "./metadata.json" + ] +} diff --git a/modules/relay/namespaces/main.bicep b/modules/relay/namespaces/main.bicep new file mode 100644 index 0000000000..9c7178f247 --- /dev/null +++ b/modules/relay/namespaces/main.bicep @@ -0,0 +1,306 @@ +@description('Required. Name of the Relay Namespace.') +@minLength(6) +@maxLength(50) +param name string + +@description('Optional. Location for all resources.') +param location string = resourceGroup().location + +@description('Optional. Name of this SKU.') +@allowed([ + 'Standard' +]) +param skuName string = 'Standard' + +@description('Optional. Authorization Rules for the Relay namespace.') +param authorizationRules array = [ + { + name: 'RootManageSharedAccessKey' + rights: [ + 'Listen' + 'Manage' + 'Send' + ] + } +] + +@description('Optional. Specifies the number of days that logs will be kept for; a value of 0 will retain data indefinitely.') +@minValue(0) +@maxValue(365) +param diagnosticLogsRetentionInDays int = 365 + +@description('Optional. Resource ID of the diagnostic storage account.') +param diagnosticStorageAccountId string = '' + +@description('Optional. Resource ID of the diagnostic log analytics workspace.') +param diagnosticWorkspaceId string = '' + +@description('Optional. Resource ID of the diagnostic event hub authorization rule for the Event Hubs namespace in which the event hub should be created or streamed to.') +param diagnosticEventHubAuthorizationRuleId string = '' + +@description('Optional. Name of the diagnostic event hub within the namespace to which logs are streamed. Without this, an event hub is created for each log category.') +param diagnosticEventHubName string = '' + +@allowed([ + '' + 'CanNotDelete' + 'ReadOnly' +]) +@description('Optional. Specify the type of lock.') +param lock string = '' + +@description('Optional. Array of role assignment objects that contain the \'roleDefinitionIdOrName\' and \'principalId\' to define RBAC role assignments on this resource. In the roleDefinitionIdOrName attribute, you can provide either the display name of the role definition, or its fully qualified ID in the following format: \'/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11\'.') +param roleAssignments array = [] + +@description('Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible.') +param privateEndpoints array = [] + +@description('Optional. Configure networking options for Relay. This object contains IPs/Subnets to allow or restrict access to private endpoints only. For security reasons, it is recommended to configure this object on the Namespace.') +param networkRuleSets object = {} + +@description('Optional. Tags of the resource.') +param tags object = {} + +@description('Optional. Enable telemetry via a Globally Unique Identifier (GUID).') +param enableDefaultTelemetry bool = true + +@description('Optional. The hybrid connections to create in the relay namespace.') +param hybridConnections array = [] + +@description('Optional. The wcf relays to create in the relay namespace.') +param wcfRelays array = [] + +@description('Optional. The name of logs that will be streamed. "allLogs" includes all possible logs for the resource.') +@allowed([ + 'allLogs' + 'OperationalLogs' + 'hybridConnectionsEvent' +]) +param diagnosticLogCategoriesToEnable array = [ + 'allLogs' + 'hybridConnectionsEvent' +] + +@description('Optional. The name of metrics that will be streamed.') +@allowed([ + 'AllMetrics' +]) +param diagnosticMetricsToEnable array = [ + 'AllMetrics' +] + +@description('Optional. The name of the diagnostic setting, if deployed. If left empty, it defaults to "-diagnosticSettings".') +param diagnosticSettingsName string = '' + +var diagnosticsLogsSpecified = [for category in filter(diagnosticLogCategoriesToEnable, item => item != 'allLogs'): { + category: category + enabled: true + retentionPolicy: { + enabled: true + days: diagnosticLogsRetentionInDays + } +}] + +var diagnosticsLogs = contains(diagnosticLogCategoriesToEnable, 'allLogs') ? [ + { + categoryGroup: 'allLogs' + enabled: true + retentionPolicy: { + enabled: true + days: diagnosticLogsRetentionInDays + } + } +] : diagnosticsLogsSpecified + +var diagnosticsMetrics = [for metric in diagnosticMetricsToEnable: { + category: metric + timeGrain: null + enabled: true + retentionPolicy: { + enabled: true + days: diagnosticLogsRetentionInDays + } +}] + +var enableReferencedModulesTelemetry = false + +resource defaultTelemetry 'Microsoft.Resources/deployments@2021-04-01' = if (enableDefaultTelemetry) { + name: 'pid-47ed15a6-730a-4827-bcb4-0fd963ffbd82-${uniqueString(deployment().name, location)}' + properties: { + mode: 'Incremental' + template: { + '$schema': 'https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#' + contentVersion: '1.0.0.0' + resources: [] + } + } +} + +resource namespace 'Microsoft.Relay/namespaces@2021-11-01' = { + name: name + location: location + tags: empty(tags) ? null : tags + sku: { + name: skuName + } + properties: {} +} + +module namespace_authorizationRules 'authorization-rules/main.bicep' = [for (authorizationRule, index) in authorizationRules: { + name: '${uniqueString(deployment().name, location)}-AuthorizationRules-${index}' + params: { + namespaceName: namespace.name + name: authorizationRule.name + rights: contains(authorizationRule, 'rights') ? authorizationRule.rights : [] + enableDefaultTelemetry: enableReferencedModulesTelemetry + } +}] + +module namespace_networkRuleSet 'network-rule-sets/main.bicep' = if (!empty(networkRuleSets) || !empty(privateEndpoints)) { + name: '${uniqueString(deployment().name, location)}-NetworkRuleSet' + params: { + namespaceName: namespace.name + publicNetworkAccess: contains(networkRuleSets, 'publicNetworkAccess') ? networkRuleSets.publicNetworkAccess : (!empty(privateEndpoints) && empty(networkRuleSets) ? 'Disabled' : 'Enabled') + defaultAction: contains(networkRuleSets, 'defaultAction') ? networkRuleSets.defaultAction : 'Allow' + ipRules: contains(networkRuleSets, 'ipRules') ? networkRuleSets.ipRules : [] + enableDefaultTelemetry: enableReferencedModulesTelemetry + } +} + +module namespace_hybridConnections 'hybrid-connections/main.bicep' = [for (hybridConnection, index) in hybridConnections: { + name: '${uniqueString(deployment().name, location)}-HybridConnection-${index}' + params: { + namespaceName: namespace.name + name: hybridConnection.name + authorizationRules: contains(hybridConnection, 'authorizationRules') ? hybridConnection.authorizationRules : [ + { + name: 'RootManageSharedAccessKey' + rights: [ + 'Listen' + 'Manage' + 'Send' + ] + } + { + name: 'defaultListener' + rights: [ + 'Listen' + ] + } + { + name: 'defaultSender' + rights: [ + 'Send' + ] + } + ] + requiresClientAuthorization: contains(hybridConnection, 'requiresClientAuthorization') ? hybridConnection.requiresClientAuthorization : true + userMetadata: hybridConnection.userMetadata + enableDefaultTelemetry: enableReferencedModulesTelemetry + } +}] + +module namespace_wcfRelays 'wcf-relays/main.bicep' = [for (wcfRelay, index) in wcfRelays: { + name: '${uniqueString(deployment().name, location)}-WcfRelay-${index}' + params: { + namespaceName: namespace.name + name: wcfRelay.name + authorizationRules: contains(wcfRelay, 'authorizationRules') ? wcfRelay.authorizationRules : [ + { + name: 'RootManageSharedAccessKey' + rights: [ + 'Listen' + 'Manage' + 'Send' + ] + } + { + name: 'defaultListener' + rights: [ + 'Listen' + ] + } + { + name: 'defaultSender' + rights: [ + 'Send' + ] + } + ] + relayType: wcfRelay.relayType + requiresClientAuthorization: contains(wcfRelay, 'requiresClientAuthorization') ? wcfRelay.requiresClientAuthorization : true + requiresTransportSecurity: contains(wcfRelay, 'requiresTransportSecurity') ? wcfRelay.requiresTransportSecurity : true + userMetadata: contains(wcfRelay, 'userMetadata') ? wcfRelay.userMetadata : null + enableDefaultTelemetry: enableReferencedModulesTelemetry + } +}] + +resource namespace_lock 'Microsoft.Authorization/locks@2020-05-01' = if (!empty(lock)) { + name: '${namespace.name}-${lock}-lock' + properties: { + level: any(lock) + notes: lock == 'CanNotDelete' ? 'Cannot delete resource or child resources.' : 'Cannot modify the resource or child resources.' + } + scope: namespace +} + +resource namespace_diagnosticSettings 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = if (!empty(diagnosticStorageAccountId) || !empty(diagnosticWorkspaceId) || !empty(diagnosticEventHubAuthorizationRuleId) || !empty(diagnosticEventHubName)) { + name: !empty(diagnosticSettingsName) ? diagnosticSettingsName : '${name}-diagnosticSettings' + properties: { + storageAccountId: !empty(diagnosticStorageAccountId) ? diagnosticStorageAccountId : null + workspaceId: !empty(diagnosticWorkspaceId) ? diagnosticWorkspaceId : null + eventHubAuthorizationRuleId: !empty(diagnosticEventHubAuthorizationRuleId) ? diagnosticEventHubAuthorizationRuleId : null + eventHubName: !empty(diagnosticEventHubName) ? diagnosticEventHubName : null + metrics: diagnosticsMetrics + logs: diagnosticsLogs + } + scope: namespace +} + +module namespace_privateEndpoints '../../network/private-endpoints/main.bicep' = [for (privateEndpoint, index) in privateEndpoints: { + name: '${uniqueString(deployment().name, location)}-Namespace-PrivateEndpoint-${index}' + params: { + groupIds: [ + privateEndpoint.service + ] + name: contains(privateEndpoint, 'name') ? privateEndpoint.name : 'pe-${last(split(namespace.id, '/'))}-${privateEndpoint.service}-${index}' + serviceResourceId: namespace.id + subnetResourceId: privateEndpoint.subnetResourceId + enableDefaultTelemetry: enableReferencedModulesTelemetry + location: reference(split(privateEndpoint.subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location + lock: contains(privateEndpoint, 'lock') ? privateEndpoint.lock : lock + privateDnsZoneGroup: contains(privateEndpoint, 'privateDnsZoneGroup') ? privateEndpoint.privateDnsZoneGroup : {} + roleAssignments: contains(privateEndpoint, 'roleAssignments') ? privateEndpoint.roleAssignments : [] + tags: contains(privateEndpoint, 'tags') ? privateEndpoint.tags : {} + manualPrivateLinkServiceConnections: contains(privateEndpoint, 'manualPrivateLinkServiceConnections') ? privateEndpoint.manualPrivateLinkServiceConnections : [] + customDnsConfigs: contains(privateEndpoint, 'customDnsConfigs') ? privateEndpoint.customDnsConfigs : [] + ipConfigurations: contains(privateEndpoint, 'ipConfigurations') ? privateEndpoint.ipConfigurations : [] + applicationSecurityGroups: contains(privateEndpoint, 'applicationSecurityGroups') ? privateEndpoint.applicationSecurityGroups : [] + customNetworkInterfaceName: contains(privateEndpoint, 'customNetworkInterfaceName') ? privateEndpoint.customNetworkInterfaceName : '' + } +}] + +module namespace_roleAssignments '.bicep/nested_roleAssignments.bicep' = [for (roleAssignment, index) in roleAssignments: { + name: '${deployment().name}-Rbac-${index}' + params: { + description: contains(roleAssignment, 'description') ? roleAssignment.description : '' + principalIds: roleAssignment.principalIds + principalType: contains(roleAssignment, 'principalType') ? roleAssignment.principalType : '' + roleDefinitionIdOrName: roleAssignment.roleDefinitionIdOrName + condition: contains(roleAssignment, 'condition') ? roleAssignment.condition : '' + delegatedManagedIdentityResourceId: contains(roleAssignment, 'delegatedManagedIdentityResourceId') ? roleAssignment.delegatedManagedIdentityResourceId : '' + resourceId: namespace.id + } +}] + +@description('The resource ID of the deployed relay namespace.') +output resourceId string = namespace.id + +@description('The resource group of the deployed relay namespace.') +output resourceGroupName string = resourceGroup().name + +@description('The name of the deployed relay namespace.') +output name string = namespace.name + +@description('The location the resource was deployed into.') +output location string = namespace.location diff --git a/modules/relay/namespaces/metadata.json b/modules/relay/namespaces/metadata.json new file mode 100644 index 0000000000..05c36e6e89 --- /dev/null +++ b/modules/relay/namespaces/metadata.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://aka.ms/bicep-registry-module-metadata-file-schema-v2#", + "name": "Relay Namespaces", + "summary": "This module deploys a Relay Namespace", + "owner": "Azure/module-maintainers" +} diff --git a/modules/relay/namespaces/network-rule-sets/README.md b/modules/relay/namespaces/network-rule-sets/README.md new file mode 100644 index 0000000000..dbca72dddb --- /dev/null +++ b/modules/relay/namespaces/network-rule-sets/README.md @@ -0,0 +1,46 @@ +# Relay Namespace Network Rules Sets `[Microsoft.Relay/namespaces/networkRuleSets]` + +This module deploys a Relay Namespace Network Rule Set. + +## Navigation + +- [Resource Types](#Resource-Types) +- [Parameters](#Parameters) +- [Outputs](#Outputs) +- [Cross-referenced modules](#Cross-referenced-modules) + +## Resource Types + +| Resource Type | API Version | +| :-- | :-- | +| `Microsoft.Relay/namespaces/networkRuleSets` | [2021-11-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Relay/2021-11-01/namespaces/networkRuleSets) | + +## Parameters + +**Conditional parameters** + +| Parameter Name | Type | Description | +| :-- | :-- | :-- | +| `namespaceName` | string | The name of the parent Relay Namespace for the Relay Network Rule Set. Required if the template is used in a standalone deployment. | + +**Optional parameters** + +| Parameter Name | Type | Default Value | Allowed Values | Description | +| :-- | :-- | :-- | :-- | :-- | +| `defaultAction` | string | `'Allow'` | `[Allow, Deny]` | Default Action for Network Rule Set. Default is "Allow". It will not be set if publicNetworkAccess is "Disabled". Otherwise, it will be set to "Deny" if ipRules or virtualNetworkRules are being used. | +| `enableDefaultTelemetry` | bool | `True` | | Enable telemetry via a Globally Unique Identifier (GUID). | +| `ipRules` | array | `[]` | | List of IpRules. It will not be set if publicNetworkAccess is "Disabled". Otherwise, when used, defaultAction will be set to "Deny". | +| `publicNetworkAccess` | string | `'Enabled'` | `[Disabled, Enabled]` | This determines if traffic is allowed over public network. Default is "Enabled". If set to "Disabled", traffic to this namespace will be restricted over Private Endpoints only and network rules will not be applied. | + + +## Outputs + +| Output Name | Type | Description | +| :-- | :-- | :-- | +| `name` | string | The name of the network rule set. | +| `resourceGroupName` | string | The name of the resource group the network rule set was created in. | +| `resourceId` | string | The resource ID of the network rule set. | + +## Cross-referenced modules + +_None_ diff --git a/modules/relay/namespaces/network-rule-sets/main.bicep b/modules/relay/namespaces/network-rule-sets/main.bicep new file mode 100644 index 0000000000..7b01405643 --- /dev/null +++ b/modules/relay/namespaces/network-rule-sets/main.bicep @@ -0,0 +1,59 @@ +@description('Conditional. The name of the parent Relay Namespace for the Relay Network Rule Set. Required if the template is used in a standalone deployment.') +@minLength(6) +@maxLength(50) +param namespaceName string + +@allowed([ + 'Enabled' + 'Disabled' +]) +@description('Optional. This determines if traffic is allowed over public network. Default is "Enabled". If set to "Disabled", traffic to this namespace will be restricted over Private Endpoints only and network rules will not be applied.') +param publicNetworkAccess string = 'Enabled' + +@allowed([ + 'Allow' + 'Deny' +]) +@description('Optional. Default Action for Network Rule Set. Default is "Allow". It will not be set if publicNetworkAccess is "Disabled". Otherwise, it will be set to "Deny" if ipRules or virtualNetworkRules are being used.') +param defaultAction string = 'Allow' + +@description('Optional. List of IpRules. It will not be set if publicNetworkAccess is "Disabled". Otherwise, when used, defaultAction will be set to "Deny".') +param ipRules array = [] + +@description('Optional. Enable telemetry via a Globally Unique Identifier (GUID).') +param enableDefaultTelemetry bool = true + +resource defaultTelemetry 'Microsoft.Resources/deployments@2021-04-01' = if (enableDefaultTelemetry) { + name: 'pid-47ed15a6-730a-4827-bcb4-0fd963ffbd82-${uniqueString(deployment().name)}' + properties: { + mode: 'Incremental' + template: { + '$schema': 'https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#' + contentVersion: '1.0.0.0' + resources: [] + } + } +} + +resource namespace 'Microsoft.Relay/namespaces@2021-11-01' existing = { + name: namespaceName +} + +resource networkRuleSet 'Microsoft.Relay/namespaces/networkRuleSets@2021-11-01' = { + name: 'default' + parent: namespace + properties: { + publicNetworkAccess: publicNetworkAccess + defaultAction: publicNetworkAccess == 'Disabled' ? null : (!empty(ipRules) ? 'Deny' : defaultAction) + ipRules: publicNetworkAccess == 'Disabled' ? null : ipRules + } +} + +@description('The name of the network rule set.') +output name string = networkRuleSet.name + +@description('The resource ID of the network rule set.') +output resourceId string = networkRuleSet.id + +@description('The name of the resource group the network rule set was created in.') +output resourceGroupName string = resourceGroup().name diff --git a/modules/relay/namespaces/network-rule-sets/metadata.json b/modules/relay/namespaces/network-rule-sets/metadata.json new file mode 100644 index 0000000000..b9dca3e86f --- /dev/null +++ b/modules/relay/namespaces/network-rule-sets/metadata.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://aka.ms/bicep-registry-module-metadata-file-schema-v2#", + "name": "Relay Namespace Network Rules Sets", + "summary": "This module deploys a Relay Namespace Network Rule Set.", + "owner": "Azure/module-maintainers" +} diff --git a/modules/relay/namespaces/network-rule-sets/version.json b/modules/relay/namespaces/network-rule-sets/version.json new file mode 100644 index 0000000000..4f6911e8a0 --- /dev/null +++ b/modules/relay/namespaces/network-rule-sets/version.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://aka.ms/bicep-registry-module-version-file-schema#", + "version": "0.2", + "pathFilters": [ + "./main.json", + "./metadata.json" + ] +} diff --git a/modules/relay/namespaces/version.json b/modules/relay/namespaces/version.json new file mode 100644 index 0000000000..3376ee6961 --- /dev/null +++ b/modules/relay/namespaces/version.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://aka.ms/bicep-registry-module-version-file-schema#", + "version": "0.5", + "pathFilters": [ + "./main.json", + "./metadata.json" + ] +} diff --git a/modules/relay/namespaces/wcf-relays/.bicep/nested_roleAssignments.bicep b/modules/relay/namespaces/wcf-relays/.bicep/nested_roleAssignments.bicep new file mode 100644 index 0000000000..b3be79a81f --- /dev/null +++ b/modules/relay/namespaces/wcf-relays/.bicep/nested_roleAssignments.bicep @@ -0,0 +1,72 @@ +@sys.description('Required. The IDs of the principals to assign the role to.') +param principalIds array + +@sys.description('Required. The name of the role to assign. If it cannot be found you can specify the role definition ID instead.') +param roleDefinitionIdOrName string + +@sys.description('Required. The resource ID of the resource to apply the role assignment to.') +param resourceId string + +@sys.description('Optional. The principal type of the assigned principal ID.') +@allowed([ + 'ServicePrincipal' + 'Group' + 'User' + 'ForeignGroup' + 'Device' + '' +]) +param principalType string = '' + +@sys.description('Optional. The description of the role assignment.') +param description string = '' + +@sys.description('Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase "foo_storage_container".') +param condition string = '' + +@sys.description('Optional. Version of the condition.') +@allowed([ + '2.0' +]) +param conditionVersion string = '2.0' + +@sys.description('Optional. Id of the delegated managed identity resource.') +param delegatedManagedIdentityResourceId string = '' + +var builtInRoleNames = { + 'App Compliance Automation Administrator': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '0f37683f-2463-46b6-9ce7-9b788b988ba2') + 'Azure Relay Listener': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '26e0b698-aa6d-4085-9386-aadae190014d') + 'Azure Relay Owner': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '2787bf04-f1f5-4bfe-8383-c8a24483ee38') + 'Azure Relay Sender': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '26baccc8-eea7-41f1-98f4-1762cc7f685d') + Contributor: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c') + 'Log Analytics Contributor': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '92aaf0da-9dab-42b6-94a3-d43ce8d16293') + 'Log Analytics Reader': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '73c42c96-874c-492b-b04d-ab87d138a893') + 'Managed Application Contributor Role': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '641177b8-a67a-45b9-a033-47bc880bb21e') + 'Managed Application Operator Role': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'c7393b34-138c-406f-901b-d8cf2b17e6ae') + 'Managed Applications Reader': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b9331d33-8a36-4f8c-b097-4f54124fdb44') + 'Monitoring Contributor': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '749f88d5-cbae-40b8-bcfc-e573ddc772fa') + 'Monitoring Reader': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '43d0d8ad-25c7-4714-9337-8ba259a9fe05') + Owner: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635') + Reader: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7') + 'Resource Policy Contributor': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '36243c78-bf99-498c-9df9-86d9f8d28608') + 'Role Based Access Control Administrator (Preview)': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168') + 'User Access Administrator': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9') +} + +resource wcfRelay 'Microsoft.Relay/namespaces/wcfRelays@2021-11-01' existing = { + name: '${split(resourceId, '/')[8]}/${split(resourceId, '/')[10]}' +} + +resource roleAssigment 'Microsoft.Authorization/roleAssignments@2022-04-01' = [for principalId in principalIds: { + name: guid(wcfRelay.id, principalId, roleDefinitionIdOrName) + properties: { + description: description + roleDefinitionId: contains(builtInRoleNames, roleDefinitionIdOrName) ? builtInRoleNames[roleDefinitionIdOrName] : roleDefinitionIdOrName + principalId: principalId + principalType: !empty(principalType) ? any(principalType) : null + condition: !empty(condition) ? condition : null + conditionVersion: !empty(conditionVersion) && !empty(condition) ? conditionVersion : null + delegatedManagedIdentityResourceId: !empty(delegatedManagedIdentityResourceId) ? delegatedManagedIdentityResourceId : null + } + scope: wcfRelay +}] diff --git a/modules/relay/namespaces/wcf-relays/README.md b/modules/relay/namespaces/wcf-relays/README.md new file mode 100644 index 0000000000..495e2f4fa2 --- /dev/null +++ b/modules/relay/namespaces/wcf-relays/README.md @@ -0,0 +1,118 @@ +# Relay Namespace WCF Relays `[Microsoft.Relay/namespaces/wcfRelays]` + +This module deploys a Relay Namespace WCF Relay. + +## Navigation + +- [Resource types](#Resource-types) +- [Parameters](#Parameters) +- [Outputs](#Outputs) +- [Cross-referenced modules](#Cross-referenced-modules) + +## Resource types + +| Resource Type | API Version | +| :-- | :-- | +| `Microsoft.Authorization/locks` | [2020-05-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Authorization/2020-05-01/locks) | +| `Microsoft.Authorization/roleAssignments` | [2022-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Authorization/2022-04-01/roleAssignments) | +| `Microsoft.Relay/namespaces/wcfRelays` | [2021-11-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Relay/2021-11-01/namespaces/wcfRelays) | +| `Microsoft.Relay/namespaces/wcfRelays/authorizationRules` | [2021-11-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Relay/2021-11-01/namespaces/wcfRelays/authorizationRules) | + +## Parameters + +**Required parameters** + +| Parameter Name | Type | Allowed Values | Description | +| :-- | :-- | :-- | :-- | +| `name` | string | | Name of the WCF Relay. | +| `relayType` | string | `[Http, NetTcp]` | Type of WCF Relay. | + +**Conditional parameters** + +| Parameter Name | Type | Description | +| :-- | :-- | :-- | +| `namespaceName` | string | The name of the parent Relay Namespace for the WCF Relay. Required if the template is used in a standalone deployment. | + +**Optional parameters** + +| Parameter Name | Type | Default Value | Allowed Values | Description | +| :-- | :-- | :-- | :-- | :-- | +| `authorizationRules` | _[authorizationRules](authorization-rules/README.md)_ array | `[System.Management.Automation.OrderedHashtable, System.Management.Automation.OrderedHashtable, System.Management.Automation.OrderedHashtable]` | | Authorization Rules for the WCF Relay. | +| `enableDefaultTelemetry` | bool | `True` | | Enable telemetry via a Globally Unique Identifier (GUID). | +| `lock` | string | `''` | `['', CanNotDelete, ReadOnly]` | Specify the type of lock. | +| `requiresClientAuthorization` | bool | `True` | | A value indicating if this relay requires client authorization. | +| `requiresTransportSecurity` | bool | `True` | | A value indicating if this relay requires transport security. | +| `roleAssignments` | array | `[]` | | Array of role assignment objects that contain the 'roleDefinitionIdOrName' and 'principalId' to define RBAC role assignments on this resource. In the roleDefinitionIdOrName attribute, you can provide either the display name of the role definition, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'. | +| `userMetadata` | string | `''` | | User-defined string data for the WCF Relay. | + + +### Parameter Usage: `roleAssignments` + +Create a role assignment for the given resource. If you want to assign a service principal / managed identity that is created in the same deployment, make sure to also specify the `'principalType'` parameter and set it to `'ServicePrincipal'`. This will ensure the role assignment waits for the principal's propagation in Azure. + +

+ +Parameter JSON format + +```json +"roleAssignments": { + "value": [ + { + "roleDefinitionIdOrName": "Reader", + "description": "Reader Role Assignment", + "principalIds": [ + "12345678-1234-1234-1234-123456789012", // object 1 + "78945612-1234-1234-1234-123456789012" // object 2 + ] + }, + { + "roleDefinitionIdOrName": "/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11", + "principalIds": [ + "12345678-1234-1234-1234-123456789012" // object 1 + ], + "principalType": "ServicePrincipal" + } + ] +} +``` + +
+ +
+ +Bicep format + +```bicep +roleAssignments: [ + { + roleDefinitionIdOrName: 'Reader' + description: 'Reader Role Assignment' + principalIds: [ + '12345678-1234-1234-1234-123456789012' // object 1 + '78945612-1234-1234-1234-123456789012' // object 2 + ] + } + { + roleDefinitionIdOrName: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11' + principalIds: [ + '12345678-1234-1234-1234-123456789012' // object 1 + ] + principalType: 'ServicePrincipal' + } +] +``` + +
+

+ +## Outputs + +| Output Name | Type | Description | +| :-- | :-- | :-- | +| `name` | string | The name of the deployed wcf relay. | +| `resourceGroupName` | string | The resource group of the deployed wcf relay. | +| `resourceId` | string | The resource ID of the deployed wcf relay. | + +## Cross-referenced modules + +_None_ diff --git a/modules/relay/namespaces/wcf-relays/authorization-rules/README.md b/modules/relay/namespaces/wcf-relays/authorization-rules/README.md new file mode 100644 index 0000000000..bbdd6020cd --- /dev/null +++ b/modules/relay/namespaces/wcf-relays/authorization-rules/README.md @@ -0,0 +1,51 @@ +# WCF Relay Authorization Rules `[Microsoft.Relay/namespaces/wcfRelays/authorizationRules]` + +This module deploys a WCF Relay Authorization Rule. + +## Navigation + +- [Resource Types](#Resource-Types) +- [Parameters](#Parameters) +- [Outputs](#Outputs) +- [Cross-referenced modules](#Cross-referenced-modules) + +## Resource Types + +| Resource Type | API Version | +| :-- | :-- | +| `Microsoft.Relay/namespaces/wcfRelays/authorizationRules` | [2021-11-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Relay/2021-11-01/namespaces/wcfRelays/authorizationRules) | + +## Parameters + +**Required parameters** + +| Parameter Name | Type | Description | +| :-- | :-- | :-- | +| `name` | string | The name of the authorization rule. | + +**Conditional parameters** + +| Parameter Name | Type | Description | +| :-- | :-- | :-- | +| `namespaceName` | string | The name of the parent Relay Namespace. Required if the template is used in a standalone deployment. | +| `wcfRelayName` | string | The name of the parent Relay Namespace WCF Relay. Required if the template is used in a standalone deployment. | + +**Optional parameters** + +| Parameter Name | Type | Default Value | Allowed Values | Description | +| :-- | :-- | :-- | :-- | :-- | +| `enableDefaultTelemetry` | bool | `True` | | Enable telemetry via a Globally Unique Identifier (GUID). | +| `rights` | array | `[]` | `[Listen, Manage, Send]` | The rights associated with the rule. | + + +## Outputs + +| Output Name | Type | Description | +| :-- | :-- | :-- | +| `name` | string | The name of the authorization rule. | +| `resourceGroupName` | string | The name of the Resource Group the authorization rule was created in. | +| `resourceId` | string | The Resource ID of the authorization rule. | + +## Cross-referenced modules + +_None_ diff --git a/modules/relay/namespaces/wcf-relays/authorization-rules/main.bicep b/modules/relay/namespaces/wcf-relays/authorization-rules/main.bicep new file mode 100644 index 0000000000..a27baa2135 --- /dev/null +++ b/modules/relay/namespaces/wcf-relays/authorization-rules/main.bicep @@ -0,0 +1,56 @@ +@description('Required. The name of the authorization rule.') +param name string + +@description('Conditional. The name of the parent Relay Namespace. Required if the template is used in a standalone deployment.') +param namespaceName string + +@description('Conditional. The name of the parent Relay Namespace WCF Relay. Required if the template is used in a standalone deployment.') +param wcfRelayName string + +@description('Optional. The rights associated with the rule.') +@allowed([ + 'Listen' + 'Manage' + 'Send' +]) +param rights array = [] + +@description('Optional. Enable telemetry via a Globally Unique Identifier (GUID).') +param enableDefaultTelemetry bool = true + +resource defaultTelemetry 'Microsoft.Resources/deployments@2021-04-01' = if (enableDefaultTelemetry) { + name: 'pid-47ed15a6-730a-4827-bcb4-0fd963ffbd82-${uniqueString(deployment().name)}' + properties: { + mode: 'Incremental' + template: { + '$schema': 'https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#' + contentVersion: '1.0.0.0' + resources: [] + } + } +} + +resource namespace 'Microsoft.Relay/namespaces@2021-11-01' existing = { + name: namespaceName + + resource wcfRelay 'wcfRelays@2021-11-01' existing = { + name: wcfRelayName + } +} + +resource authorizationRule 'Microsoft.Relay/namespaces/wcfRelays/authorizationRules@2021-11-01' = { + name: name + parent: namespace::wcfRelay + properties: { + rights: rights + } +} + +@description('The name of the authorization rule.') +output name string = authorizationRule.name + +@description('The Resource ID of the authorization rule.') +output resourceId string = authorizationRule.id + +@description('The name of the Resource Group the authorization rule was created in.') +output resourceGroupName string = resourceGroup().name diff --git a/modules/relay/namespaces/wcf-relays/authorization-rules/metadata.json b/modules/relay/namespaces/wcf-relays/authorization-rules/metadata.json new file mode 100644 index 0000000000..18f35ae6e9 --- /dev/null +++ b/modules/relay/namespaces/wcf-relays/authorization-rules/metadata.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://aka.ms/bicep-registry-module-metadata-file-schema-v2#", + "name": "WCF Relay Authorization Rules", + "summary": "This module deploys a WCF Relay Authorization Rule.", + "owner": "Azure/module-maintainers" +} diff --git a/modules/relay/namespaces/wcf-relays/authorization-rules/version.json b/modules/relay/namespaces/wcf-relays/authorization-rules/version.json new file mode 100644 index 0000000000..5b9f717b34 --- /dev/null +++ b/modules/relay/namespaces/wcf-relays/authorization-rules/version.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://aka.ms/bicep-registry-module-version-file-schema#", + "version": "0.4", + "pathFilters": [ + "./main.json", + "./metadata.json" + ] +} diff --git a/modules/relay/namespaces/wcf-relays/main.bicep b/modules/relay/namespaces/wcf-relays/main.bicep new file mode 100644 index 0000000000..33138fa634 --- /dev/null +++ b/modules/relay/namespaces/wcf-relays/main.bicep @@ -0,0 +1,134 @@ +@description('Conditional. The name of the parent Relay Namespace for the WCF Relay. Required if the template is used in a standalone deployment.') +@minLength(6) +@maxLength(50) +param namespaceName string + +@description('Required. Name of the WCF Relay.') +@minLength(6) +@maxLength(50) +param name string + +@allowed([ + 'Http' + 'NetTcp' +]) +@description('Required. Type of WCF Relay.') +param relayType string + +@description('Optional. A value indicating if this relay requires client authorization.') +param requiresClientAuthorization bool = true + +@description('Optional. A value indicating if this relay requires transport security.') +param requiresTransportSecurity bool = true + +@description('Optional. User-defined string data for the WCF Relay.') +param userMetadata string = '' + +@description('Optional. Authorization Rules for the WCF Relay.') +param authorizationRules array = [ + { + name: 'RootManageSharedAccessKey' + rights: [ + 'Listen' + 'Manage' + 'Send' + ] + } + { + name: 'defaultListener' + rights: [ + 'Listen' + ] + } + { + name: 'defaultSender' + rights: [ + 'Send' + ] + } +] + +@allowed([ + '' + 'CanNotDelete' + 'ReadOnly' +]) +@description('Optional. Specify the type of lock.') +param lock string = '' + +@description('Optional. Array of role assignment objects that contain the \'roleDefinitionIdOrName\' and \'principalId\' to define RBAC role assignments on this resource. In the roleDefinitionIdOrName attribute, you can provide either the display name of the role definition, or its fully qualified ID in the following format: \'/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11\'.') +param roleAssignments array = [] + +@description('Optional. Enable telemetry via a Globally Unique Identifier (GUID).') +param enableDefaultTelemetry bool = true + +var enableReferencedModulesTelemetry = false + +resource defaultTelemetry 'Microsoft.Resources/deployments@2021-04-01' = if (enableDefaultTelemetry) { + name: 'pid-47ed15a6-730a-4827-bcb4-0fd963ffbd82-${uniqueString(deployment().name)}' + properties: { + mode: 'Incremental' + template: { + '$schema': 'https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#' + contentVersion: '1.0.0.0' + resources: [] + } + } +} + +resource namespace 'Microsoft.Relay/namespaces@2021-11-01' existing = { + name: namespaceName +} + +resource wcfRelay 'Microsoft.Relay/namespaces/wcfRelays@2021-11-01' = { + name: name + parent: namespace + properties: { + relayType: relayType + requiresClientAuthorization: requiresClientAuthorization + requiresTransportSecurity: requiresTransportSecurity + userMetadata: !empty(userMetadata) ? userMetadata : null + } +} + +module wcfRelay_authorizationRules 'authorization-rules/main.bicep' = [for (authorizationRule, index) in authorizationRules: { + name: '${deployment().name}-AuthorizationRule-${index}' + params: { + namespaceName: namespaceName + wcfRelayName: wcfRelay.name + name: authorizationRule.name + rights: contains(authorizationRule, 'rights') ? authorizationRule.rights : [] + enableDefaultTelemetry: enableReferencedModulesTelemetry + } +}] + +resource wcfRelay_lock 'Microsoft.Authorization/locks@2020-05-01' = if (!empty(lock)) { + name: '${wcfRelay.name}-${lock}-lock' + properties: { + level: any(lock) + notes: lock == 'CanNotDelete' ? 'Cannot delete resource or child resources.' : 'Cannot modify the resource or child resources.' + } + scope: wcfRelay +} + +module wcfRelay_roleAssignments '.bicep/nested_roleAssignments.bicep' = [for (roleAssignment, index) in roleAssignments: { + name: '${deployment().name}-Rbac-${index}' + params: { + description: contains(roleAssignment, 'description') ? roleAssignment.description : '' + principalIds: roleAssignment.principalIds + principalType: contains(roleAssignment, 'principalType') ? roleAssignment.principalType : '' + roleDefinitionIdOrName: roleAssignment.roleDefinitionIdOrName + condition: contains(roleAssignment, 'condition') ? roleAssignment.condition : '' + delegatedManagedIdentityResourceId: contains(roleAssignment, 'delegatedManagedIdentityResourceId') ? roleAssignment.delegatedManagedIdentityResourceId : '' + resourceId: wcfRelay.id + } +}] + +@description('The name of the deployed wcf relay.') +output name string = wcfRelay.name + +@description('The resource ID of the deployed wcf relay.') +output resourceId string = wcfRelay.id + +@description('The resource group of the deployed wcf relay.') +output resourceGroupName string = resourceGroup().name diff --git a/modules/relay/namespaces/wcf-relays/metadata.json b/modules/relay/namespaces/wcf-relays/metadata.json new file mode 100644 index 0000000000..97d1f1e0b7 --- /dev/null +++ b/modules/relay/namespaces/wcf-relays/metadata.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://aka.ms/bicep-registry-module-metadata-file-schema-v2#", + "name": "Relay Namespace WCF Relays", + "summary": "This module deploys a Relay Namespace WCF Relay.", + "owner": "Azure/module-maintainers" +} diff --git a/modules/relay/namespaces/wcf-relays/version.json b/modules/relay/namespaces/wcf-relays/version.json new file mode 100644 index 0000000000..5b9f717b34 --- /dev/null +++ b/modules/relay/namespaces/wcf-relays/version.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://aka.ms/bicep-registry-module-version-file-schema#", + "version": "0.4", + "pathFilters": [ + "./main.json", + "./metadata.json" + ] +} diff --git a/modules/web/sites/.bicep/nested_roleAssignments.bicep b/modules/web/sites/.bicep/nested_roleAssignments.bicep index d78b6261b6..764dd19ea5 100644 --- a/modules/web/sites/.bicep/nested_roleAssignments.bicep +++ b/modules/web/sites/.bicep/nested_roleAssignments.bicep @@ -34,6 +34,7 @@ param conditionVersion string = '2.0' param delegatedManagedIdentityResourceId string = '' var builtInRoleNames = { + 'App Compliance Automation Administrator': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '0f37683f-2463-46b6-9ce7-9b788b988ba2') Contributor: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c') 'Log Analytics Contributor': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '92aaf0da-9dab-42b6-94a3-d43ce8d16293') 'Log Analytics Reader': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '73c42c96-874c-492b-b04d-ab87d138a893') diff --git a/modules/web/sites/.test/functionAppCommon/dependencies.bicep b/modules/web/sites/.test/functionAppCommon/dependencies.bicep index 6f7f7f7cb3..c58bb47c3a 100644 --- a/modules/web/sites/.test/functionAppCommon/dependencies.bicep +++ b/modules/web/sites/.test/functionAppCommon/dependencies.bicep @@ -16,6 +16,12 @@ param storageAccountName string @description('Required. The name of the Application Insights instance to create.') param applicationInsightsName string +@description('Required. The name of the Relay Namespace to create.') +param relayNamespaceName string + +@description('Required. The name of the Hybrid Connection to create.') +param hybridConnectionName string + var addressPrefix = '10.0.0.0/16' resource virtualNetwork 'Microsoft.Network/virtualNetworks@2022-01-01' = { @@ -89,6 +95,34 @@ resource applicationInsights 'Microsoft.Insights/components@2020-02-02' = { properties: {} } +resource namespace 'Microsoft.Relay/namespaces@2021-11-01' = { + name: relayNamespaceName + location: location + sku: { + name: 'Standard' + } + properties: {} +} + +resource hybridConnection 'Microsoft.Relay/namespaces/hybridConnections@2021-11-01' = { + name: hybridConnectionName + parent: namespace + properties: { + requiresClientAuthorization: true + userMetadata: '[{"key":"endpoint","value":"db-server.constoso.com:1433"}]' + } +} + +resource authorizationRule 'Microsoft.Relay/namespaces/hybridConnections/authorizationRules@2021-11-01' = { + name: 'defaultSender' + parent: hybridConnection + properties: { + rights: [ + 'Send' + ] + } +} + @description('The resource ID of the created Virtual Network Subnet.') output subnetResourceId string = virtualNetwork.properties.subnets[0].id @@ -109,3 +143,6 @@ output applicationInsightsResourceId string = applicationInsights.id @description('The resource ID of the created Private DNS Zone.') output privateDNSZoneResourceId string = privateDNSZone.id + +@description('The resource ID of the created Hybrid Connection.') +output hybridConnectionResourceId string = hybridConnection.id diff --git a/modules/web/sites/.test/functionAppCommon/main.test.bicep b/modules/web/sites/.test/functionAppCommon/main.test.bicep index 612feb3c70..5fa0c8ec43 100644 --- a/modules/web/sites/.test/functionAppCommon/main.test.bicep +++ b/modules/web/sites/.test/functionAppCommon/main.test.bicep @@ -35,11 +35,13 @@ module nestedDependencies 'dependencies.bicep' = { scope: resourceGroup name: '${uniqueString(deployment().name, location)}-nestedDependencies' params: { - virtualNetworkName: 'dep-${namePrefix}-vnet-${serviceShort}' - managedIdentityName: 'dep-${namePrefix}-msi-${serviceShort}' - serverFarmName: 'dep-${namePrefix}-sf-${serviceShort}' - storageAccountName: 'dep${namePrefix}st${serviceShort}' - applicationInsightsName: 'dep-${namePrefix}-appi-${serviceShort}' + virtualNetworkName: 'dep-<>-vnet-${serviceShort}' + managedIdentityName: 'dep-<>-msi-${serviceShort}' + serverFarmName: 'dep-<>-sf-${serviceShort}' + storageAccountName: 'dep<>st${serviceShort}' + applicationInsightsName: 'dep-<>-appi-${serviceShort}' + relayNamespaceName: 'dep-<>-ns-${serviceShort}' + hybridConnectionName: 'dep-<>-hc-${serviceShort}' } } @@ -179,5 +181,11 @@ module testDeployment '../../main.bicep' = { userAssignedIdentities: { '${nestedDependencies.outputs.managedIdentityResourceId}': {} } + hybridConnectionRelays: [ + { + resourceId: nestedDependencies.outputs.hybridConnectionResourceId + sendKeyName: 'defaultSender' + } + ] } } diff --git a/modules/web/sites/.test/webAppCommon/dependencies.bicep b/modules/web/sites/.test/webAppCommon/dependencies.bicep index 8ce6b01cc0..1642941bfb 100644 --- a/modules/web/sites/.test/webAppCommon/dependencies.bicep +++ b/modules/web/sites/.test/webAppCommon/dependencies.bicep @@ -10,6 +10,12 @@ param managedIdentityName string @description('Required. The name of the Server Farm to create.') param serverFarmName string +@description('Required. The name of the Relay Namespace to create.') +param relayNamespaceName string + +@description('Required. The name of the Hybrid Connection to create.') +param hybridConnectionName string + var addressPrefix = '10.0.0.0/16' resource virtualNetwork 'Microsoft.Network/virtualNetworks@2022-01-01' = { @@ -66,6 +72,34 @@ resource serverFarm 'Microsoft.Web/serverfarms@2022-03-01' = { properties: {} } +resource relayNamespace 'Microsoft.Relay/namespaces@2021-11-01' = { + name: relayNamespaceName + location: location + sku: { + name: 'Standard' + } + properties: {} +} + +resource hybridConnection 'Microsoft.Relay/namespaces/hybridConnections@2021-11-01' = { + name: hybridConnectionName + parent: relayNamespace + properties: { + requiresClientAuthorization: true + userMetadata: '[{"key":"endpoint","value":"db-server.constoso.com:1433"}]' + } +} + +resource authorizationRule 'Microsoft.Relay/namespaces/hybridConnections/authorizationRules@2021-11-01' = { + name: 'defaultSender' + parent: hybridConnection + properties: { + rights: [ + 'Send' + ] + } +} + @description('The resource ID of the created Virtual Network Subnet.') output subnetResourceId string = virtualNetwork.properties.subnets[0].id @@ -80,3 +114,6 @@ output serverFarmResourceId string = serverFarm.id @description('The resource ID of the created Private DNS Zone.') output privateDNSZoneResourceId string = privateDNSZone.id + +@description('The resource ID of the created Hybrid Connection.') +output hybridConnectionResourceId string = hybridConnection.id diff --git a/modules/web/sites/.test/webAppCommon/main.test.bicep b/modules/web/sites/.test/webAppCommon/main.test.bicep index 4fcba46630..63b1c2b61c 100644 --- a/modules/web/sites/.test/webAppCommon/main.test.bicep +++ b/modules/web/sites/.test/webAppCommon/main.test.bicep @@ -35,9 +35,11 @@ module nestedDependencies 'dependencies.bicep' = { scope: resourceGroup name: '${uniqueString(deployment().name, location)}-nestedDependencies' params: { - virtualNetworkName: 'dep-${namePrefix}-vnet-${serviceShort}' - managedIdentityName: 'dep-${namePrefix}-msi-${serviceShort}' - serverFarmName: 'dep-${namePrefix}-sf-${serviceShort}' + virtualNetworkName: 'dep-<>-vnet-${serviceShort}' + managedIdentityName: 'dep-<>-msi-${serviceShort}' + serverFarmName: 'dep-<>-sf-${serviceShort}' + relayNamespaceName: 'dep-<>-ns-${serviceShort}' + hybridConnectionName: 'dep-<>-hc-${serviceShort}' } } @@ -58,7 +60,6 @@ module diagnosticDependencies '../../../../.shared/.templates/diagnostic.depende // ============== // // Test Execution // // ============== // - module testDeployment '../../main.bicep' = { scope: resourceGroup name: '${uniqueString(deployment().name, location)}-test-${serviceShort}' @@ -114,6 +115,12 @@ module testDeployment '../../main.bicep' = { } ] } + hybridConnectionRelays: [ + { + resourceId: nestedDependencies.outputs.hybridConnectionResourceId + sendKeyName: 'defaultSender' + } + ] } { name: 'slot2' @@ -165,5 +172,11 @@ module testDeployment '../../main.bicep' = { } ] + hybridConnectionRelays: [ + { + resourceId: nestedDependencies.outputs.hybridConnectionResourceId + sendKeyName: 'defaultSender' + } + ] } } diff --git a/modules/web/sites/README.md b/modules/web/sites/README.md index efa5cc5f16..0155df5100 100644 --- a/modules/web/sites/README.md +++ b/modules/web/sites/README.md @@ -24,8 +24,10 @@ This module deploys a Web or Function App. | `Microsoft.Web/sites` | [2021-03-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Web/2021-03-01/sites) | | `Microsoft.Web/sites/basicPublishingCredentialsPolicies` | [2022-03-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Web/sites) | | `Microsoft.Web/sites/config` | [2022-03-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Web/sites) | +| `Microsoft.Web/sites/hybridConnectionNamespaces/relays` | [2022-03-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Web/2022-03-01/sites/hybridConnectionNamespaces/relays) | | `Microsoft.Web/sites/slots` | [2022-03-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Web/2022-03-01/sites/slots) | | `Microsoft.Web/sites/slots/config` | [2022-03-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Web/sites) | +| `Microsoft.Web/sites/slots/hybridConnectionNamespaces/relays` | [2022-03-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Web/2022-03-01/sites/slots/hybridConnectionNamespaces/relays) | ## Parameters @@ -66,6 +68,7 @@ This module deploys a Web or Function App. | `enableDefaultTelemetry` | bool | `True` | | Enable telemetry via a Globally Unique Identifier (GUID). | | `hostNameSslStates` | array | `[]` | | Hostname SSL states are used to manage the SSL bindings for app's hostnames. | | `httpsOnly` | bool | `True` | | Configures a site to accept only HTTPS requests. Issues redirect for HTTP requests. | +| `hybridConnectionRelays` | array | `[]` | | Names of hybrid connection relays to connect app with. | | `hyperV` | bool | `False` | | Hyper-V sandbox. | | `keyVaultAccessIdentityResourceId` | string | `''` | | The resource ID of the assigned identity to be used to access a key vault with. | | `location` | string | `[resourceGroup().location]` | | Location for all Resources. | @@ -536,6 +539,12 @@ module sites './web/sites/main.bicep' = { diagnosticStorageAccountId: '' diagnosticWorkspaceId: '' enableDefaultTelemetry: '' + hybridConnectionRelays: [ + { + resourceId: '' + sendKeyName: 'defaultSender' + } + ] keyVaultAccessIdentityResourceId: '' lock: 'CanNotDelete' privateEndpoints: [ @@ -692,6 +701,14 @@ module sites './web/sites/main.bicep' = { "enableDefaultTelemetry": { "value": "" }, + "hybridConnectionRelays": { + "value": [ + { + "resourceId": "", + "sendKeyName": "defaultSender" + } + ] + }, "keyVaultAccessIdentityResourceId": { "value": "" }, @@ -844,6 +861,12 @@ module sites './web/sites/main.bicep' = { diagnosticWorkspaceId: '' enableDefaultTelemetry: '' httpsOnly: true + hybridConnectionRelays: [ + { + resourceId: '' + sendKeyName: 'defaultSender' + } + ] privateEndpoints: [ { privateDnsZoneGroup: { @@ -884,6 +907,12 @@ module sites './web/sites/main.bicep' = { diagnosticLogsRetentionInDays: 7 diagnosticStorageAccountId: '' diagnosticWorkspaceId: '' + hybridConnectionRelays: [ + { + resourceId: '' + sendKeyName: 'defaultSender' + } + ] name: 'slot1' privateEndpoints: [ { @@ -985,6 +1014,14 @@ module sites './web/sites/main.bicep' = { "httpsOnly": { "value": true }, + "hybridConnectionRelays": { + "value": [ + { + "resourceId": "", + "sendKeyName": "defaultSender" + } + ] + }, "privateEndpoints": { "value": [ { @@ -1032,6 +1069,12 @@ module sites './web/sites/main.bicep' = { "diagnosticLogsRetentionInDays": 7, "diagnosticStorageAccountId": "", "diagnosticWorkspaceId": "", + "hybridConnectionRelays": [ + { + "resourceId": "", + "sendKeyName": "defaultSender" + } + ], "name": "slot1", "privateEndpoints": [ { diff --git a/modules/web/sites/hybrid-connection-namespaces/relays/README.md b/modules/web/sites/hybrid-connection-namespaces/relays/README.md new file mode 100644 index 0000000000..6a1e92b3e8 --- /dev/null +++ b/modules/web/sites/hybrid-connection-namespaces/relays/README.md @@ -0,0 +1,51 @@ +# Web/Function Apps Hybrid Connection Relay `[Microsoft.Web/sites/hybridConnectionNamespaces/relays]` + +This module deploys a Site Hybrid Connection Namespace Relay. + +## Navigation + +- [Resource Types](#Resource-Types) +- [Parameters](#Parameters) +- [Outputs](#Outputs) +- [Cross-referenced modules](#Cross-referenced-modules) + +## Resource Types + +| Resource Type | API Version | +| :-- | :-- | +| `Microsoft.Web/sites/hybridConnectionNamespaces/relays` | [2022-03-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Web/2022-03-01/sites/hybridConnectionNamespaces/relays) | + +## Parameters + +**Required parameters** + +| Parameter Name | Type | Description | +| :-- | :-- | :-- | +| `hybridConnectionResourceId` | string | The resource ID of the relay namespace hybrid connection. | + +**Conditional parameters** + +| Parameter Name | Type | Description | +| :-- | :-- | :-- | +| `appName` | string | The name of the parent web site. Required if the template is used in a standalone deployment. | + +**Optional parameters** + +| Parameter Name | Type | Default Value | Description | +| :-- | :-- | :-- | :-- | +| `enableDefaultTelemetry` | bool | `True` | Enable telemetry via a Globally Unique Identifier (GUID). | +| `location` | string | `[resourceGroup().location]` | Location for all Resources. | +| `sendKeyName` | string | `'defaultSender'` | Name of the authorization rule send key to use. | + + +## Outputs + +| Output Name | Type | Description | +| :-- | :-- | :-- | +| `name` | string | The name of the hybrid connection relay.. | +| `resourceGroupName` | string | The name of the resource group the resource was deployed into. | +| `resourceId` | string | The resource ID of the hybrid connection relay. | + +## Cross-referenced modules + +_None_ diff --git a/modules/web/sites/hybrid-connection-namespaces/relays/main.bicep b/modules/web/sites/hybrid-connection-namespaces/relays/main.bicep new file mode 100644 index 0000000000..62a4bedcea --- /dev/null +++ b/modules/web/sites/hybrid-connection-namespaces/relays/main.bicep @@ -0,0 +1,65 @@ +@description('Required. The resource ID of the relay namespace hybrid connection.') +param hybridConnectionResourceId string + +@description('Conditional. The name of the parent web site. Required if the template is used in a standalone deployment.') +param appName string + +@description('Optional. Name of the authorization rule send key to use.') +param sendKeyName string = 'defaultSender' + +@description('Optional. Location for all Resources.') +param location string = resourceGroup().location + +@description('Optional. Enable telemetry via a Globally Unique Identifier (GUID).') +param enableDefaultTelemetry bool = true + +resource defaultTelemetry 'Microsoft.Resources/deployments@2021-04-01' = if (enableDefaultTelemetry) { + name: 'pid-47ed15a6-730a-4827-bcb4-0fd963ffbd82-${uniqueString(deployment().name, location)}' + properties: { + mode: 'Incremental' + template: { + '$schema': 'https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#' + contentVersion: '1.0.0.0' + resources: [] + } + } +} + +resource namespace 'Microsoft.Relay/namespaces@2021-11-01' existing = { + name: split(hybridConnectionResourceId, '/')[8] + scope: resourceGroup(split(hybridConnectionResourceId, '/')[2], split(hybridConnectionResourceId, '/')[4]) + + resource hybridConnection 'hybridConnections@2021-11-01' existing = { + name: split(hybridConnectionResourceId, '/')[10] + + resource authorizationRule 'authorizationRules@2021-11-01' existing = { + name: sendKeyName + } + } +} + +resource hybridConnectionRelay 'Microsoft.Web/sites/hybridConnectionNamespaces/relays@2022-03-01' = { + name: '${appName}/${namespace.name}/${namespace::hybridConnection.name}' + properties: { + serviceBusNamespace: namespace.name + serviceBusSuffix: split(substring(namespace.properties.serviceBusEndpoint, indexOf(namespace.properties.serviceBusEndpoint, '.servicebus')), ':')[0] + relayName: namespace::hybridConnection.name + relayArmUri: namespace::hybridConnection.id + hostname: split(json(namespace::hybridConnection.properties.userMetadata)[0].value, ':')[0] + port: int(split(json(namespace::hybridConnection.properties.userMetadata)[0].value, ':')[1]) + sendKeyName: namespace::hybridConnection::authorizationRule.name + sendKeyValue: namespace::hybridConnection::authorizationRule.listKeys().primaryKey + } +} + +// =========== // +// Outputs // +// =========== // +@description('The name of the hybrid connection relay..') +output name string = hybridConnectionRelay.name + +@description('The resource ID of the hybrid connection relay.') +output resourceId string = hybridConnectionRelay.id + +@description('The name of the resource group the resource was deployed into.') +output resourceGroupName string = resourceGroup().name diff --git a/modules/web/sites/hybrid-connection-namespaces/relays/metadata.json b/modules/web/sites/hybrid-connection-namespaces/relays/metadata.json new file mode 100644 index 0000000000..afb40f9ec0 --- /dev/null +++ b/modules/web/sites/hybrid-connection-namespaces/relays/metadata.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://aka.ms/bicep-registry-module-metadata-file-schema-v2#", + "name": "Web/Function Apps Hybrid Connection Relay", + "summary": "This module deploys a Site Hybrid Connection Namespace Relay.", + "owner": "Azure/module-maintainers" +} diff --git a/modules/web/sites/hybrid-connection-namespaces/relays/version.json b/modules/web/sites/hybrid-connection-namespaces/relays/version.json new file mode 100644 index 0000000000..039073633f --- /dev/null +++ b/modules/web/sites/hybrid-connection-namespaces/relays/version.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://aka.ms/bicep-registry-module-version-file-schema#", + "version": "0.1", + "pathFilters": [ + "./main.json", + "./metadata.json" + ] +} diff --git a/modules/web/sites/main.bicep b/modules/web/sites/main.bicep index 808d0d33fb..0cbea34d8b 100644 --- a/modules/web/sites/main.bicep +++ b/modules/web/sites/main.bicep @@ -195,6 +195,9 @@ param redundancyMode string = 'None' @description('Optional. The site publishing credential policy names which are associated with the sites.') param basicPublishingCredentialsPolicies array = [] +@description('Optional. Names of hybrid connection relays to connect app with.') +param hybridConnectionRelays array = [] + // =========== // // Variables // // =========== // @@ -356,6 +359,7 @@ module app_slots 'slots/main.bicep' = [for (slot, index) in slots: { vnetContentShareEnabled: contains(slot, 'vnetContentShareEnabled') ? slot.vnetContentShareEnabled : false vnetImagePullEnabled: contains(slot, 'vnetImagePullEnabled') ? slot.vnetImagePullEnabled : false vnetRouteAllEnabled: contains(slot, 'vnetRouteAllEnabled') ? slot.vnetRouteAllEnabled : false + hybridConnectionRelays: contains(slot, 'hybridConnectionRelays') ? slot.hybridConnectionRelays : [] } }] @@ -368,6 +372,16 @@ module app_basicPublishingCredentialsPolicies 'basic-publishing-credentials-poli } }] +module app_hybridConnectionRelays 'hybrid-connection-namespaces/relays/main.bicep' = [for (hybridConnectionRelay, index) in hybridConnectionRelays: { + name: '${uniqueString(deployment().name, location)}-HybridConnectionRelay-${index}' + params: { + hybridConnectionResourceId: hybridConnectionRelay.resourceId + appName: app.name + sendKeyName: contains(hybridConnectionRelay, 'sendKeyName') ? hybridConnectionRelay.sendKeyName : null + enableDefaultTelemetry: enableReferencedModulesTelemetry + } +}] + resource app_lock 'Microsoft.Authorization/locks@2020-05-01' = if (!empty(lock)) { name: '${app.name}-${lock}-lock' properties: { diff --git a/modules/web/sites/slots/.bicep/nested_roleAssignments.bicep b/modules/web/sites/slots/.bicep/nested_roleAssignments.bicep index c504349543..59c7b550b8 100644 --- a/modules/web/sites/slots/.bicep/nested_roleAssignments.bicep +++ b/modules/web/sites/slots/.bicep/nested_roleAssignments.bicep @@ -22,6 +22,7 @@ param principalType string = '' param description string = '' var builtInRoleNames = { + 'App Compliance Automation Administrator': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '0f37683f-2463-46b6-9ce7-9b788b988ba2') Contributor: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c') 'Log Analytics Contributor': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '92aaf0da-9dab-42b6-94a3-d43ce8d16293') 'Log Analytics Reader': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '73c42c96-874c-492b-b04d-ab87d138a893') diff --git a/modules/web/sites/slots/README.md b/modules/web/sites/slots/README.md index c222894579..9c22326c52 100644 --- a/modules/web/sites/slots/README.md +++ b/modules/web/sites/slots/README.md @@ -22,6 +22,7 @@ This module deploys a Web or Function App Deployment Slot. | `Microsoft.Network/privateEndpoints/privateDnsZoneGroups` | [2022-07-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Network/2022-07-01/privateEndpoints/privateDnsZoneGroups) | | `Microsoft.Web/sites/slots` | [2022-03-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Web/2022-03-01/sites/slots) | | `Microsoft.Web/sites/slots/config` | [2022-03-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Web/sites) | +| `Microsoft.Web/sites/slots/hybridConnectionNamespaces/relays` | [2022-03-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Web/2022-03-01/sites/slots/hybridConnectionNamespaces/relays) | ## Parameters @@ -66,6 +67,7 @@ This module deploys a Web or Function App Deployment Slot. | `enableDefaultTelemetry` | bool | `True` | | Enable telemetry via the Customer Usage Attribution ID (GUID). | | `hostNameSslStates` | array | `[]` | | Hostname SSL states are used to manage the SSL bindings for app's hostnames. | | `httpsOnly` | bool | `True` | | Configures a slot to accept only HTTPS requests. Issues redirect for HTTP requests. | +| `hybridConnectionRelays` | array | `[]` | | Names of hybrid connection relays to connect app with. | | `hyperV` | bool | `False` | | Hyper-V sandbox. | | `keyVaultAccessIdentityResourceId` | string | `''` | | The resource ID of the assigned identity to be used to access a key vault with. | | `location` | string | `[resourceGroup().location]` | | Location for all Resources. | diff --git a/modules/web/sites/slots/hybrid-connection-namespaces/relays/README.md b/modules/web/sites/slots/hybrid-connection-namespaces/relays/README.md new file mode 100644 index 0000000000..cad4fbb37d --- /dev/null +++ b/modules/web/sites/slots/hybrid-connection-namespaces/relays/README.md @@ -0,0 +1,52 @@ +# Web/Function Apps Slot Hybrid Connection Relay `[Microsoft.Web/sites/slots/hybridConnectionNamespaces/relays]` + +This module deploys a Site Slot Hybrid Connection Namespace Relay. + +## Navigation + +- [Resource Types](#Resource-Types) +- [Parameters](#Parameters) +- [Outputs](#Outputs) +- [Cross-referenced modules](#Cross-referenced-modules) + +## Resource Types + +| Resource Type | API Version | +| :-- | :-- | +| `Microsoft.Web/sites/slots/hybridConnectionNamespaces/relays` | [2022-03-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Web/2022-03-01/sites/slots/hybridConnectionNamespaces/relays) | + +## Parameters + +**Required parameters** + +| Parameter Name | Type | Description | +| :-- | :-- | :-- | +| `hybridConnectionResourceId` | string | The resource ID of the relay namespace hybrid connection. | + +**Conditional parameters** + +| Parameter Name | Type | Description | +| :-- | :-- | :-- | +| `appName` | string | The name of the parent web site. Required if the template is used in a standalone deployment. | +| `slotName` | string | The name of the site slot. Required if the template is used in a standalone deployment. | + +**Optional parameters** + +| Parameter Name | Type | Default Value | Description | +| :-- | :-- | :-- | :-- | +| `enableDefaultTelemetry` | bool | `True` | Enable telemetry via a Globally Unique Identifier (GUID). | +| `location` | string | `[resourceGroup().location]` | Location for all Resources. | +| `sendKeyName` | string | `'defaultSender'` | Name of the authorization rule send key to use. | + + +## Outputs + +| Output Name | Type | Description | +| :-- | :-- | :-- | +| `name` | string | The name of the hybrid connection relay.. | +| `resourceGroupName` | string | The name of the resource group the resource was deployed into. | +| `resourceId` | string | The resource ID of the hybrid connection relay. | + +## Cross-referenced modules + +_None_ diff --git a/modules/web/sites/slots/hybrid-connection-namespaces/relays/main.bicep b/modules/web/sites/slots/hybrid-connection-namespaces/relays/main.bicep new file mode 100644 index 0000000000..e2b5416953 --- /dev/null +++ b/modules/web/sites/slots/hybrid-connection-namespaces/relays/main.bicep @@ -0,0 +1,68 @@ +@description('Required. The resource ID of the relay namespace hybrid connection.') +param hybridConnectionResourceId string + +@description('Conditional. The name of the site slot. Required if the template is used in a standalone deployment.') +param slotName string + +@description('Conditional. The name of the parent web site. Required if the template is used in a standalone deployment.') +param appName string + +@description('Optional. Name of the authorization rule send key to use.') +param sendKeyName string = 'defaultSender' + +@description('Optional. Location for all Resources.') +param location string = resourceGroup().location + +@description('Optional. Enable telemetry via a Globally Unique Identifier (GUID).') +param enableDefaultTelemetry bool = true + +resource defaultTelemetry 'Microsoft.Resources/deployments@2021-04-01' = if (enableDefaultTelemetry) { + name: 'pid-47ed15a6-730a-4827-bcb4-0fd963ffbd82-${uniqueString(deployment().name, location)}' + properties: { + mode: 'Incremental' + template: { + '$schema': 'https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#' + contentVersion: '1.0.0.0' + resources: [] + } + } +} + +resource namespace 'Microsoft.Relay/namespaces@2021-11-01' existing = { + name: split(hybridConnectionResourceId, '/')[8] + scope: resourceGroup(split(hybridConnectionResourceId, '/')[2], split(hybridConnectionResourceId, '/')[4]) + + resource hybridConnection 'hybridConnections@2021-11-01' existing = { + name: split(hybridConnectionResourceId, '/')[10] + + resource authorizationRule 'authorizationRules@2021-11-01' existing = { + name: sendKeyName + } + } +} + +resource hybridConnectionRelay 'Microsoft.Web/sites/slots/hybridConnectionNamespaces/relays@2022-03-01' = { + name: '${appName}/${slotName}/${namespace.name}/${namespace::hybridConnection.name}' + properties: { + serviceBusNamespace: namespace.name + serviceBusSuffix: split(substring(namespace.properties.serviceBusEndpoint, indexOf(namespace.properties.serviceBusEndpoint, '.servicebus')), ':')[0] + relayName: namespace::hybridConnection.name + relayArmUri: namespace::hybridConnection.id + hostname: split(json(namespace::hybridConnection.properties.userMetadata)[0].value, ':')[0] + port: int(split(json(namespace::hybridConnection.properties.userMetadata)[0].value, ':')[1]) + sendKeyName: namespace::hybridConnection::authorizationRule.name + sendKeyValue: namespace::hybridConnection::authorizationRule.listKeys().primaryKey + } +} + +// =========== // +// Outputs // +// =========== // +@description('The name of the hybrid connection relay..') +output name string = hybridConnectionRelay.name + +@description('The resource ID of the hybrid connection relay.') +output resourceId string = hybridConnectionRelay.id + +@description('The name of the resource group the resource was deployed into.') +output resourceGroupName string = resourceGroup().name diff --git a/modules/web/sites/slots/hybrid-connection-namespaces/relays/metadata.json b/modules/web/sites/slots/hybrid-connection-namespaces/relays/metadata.json new file mode 100644 index 0000000000..ea9a18c024 --- /dev/null +++ b/modules/web/sites/slots/hybrid-connection-namespaces/relays/metadata.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://aka.ms/bicep-registry-module-metadata-file-schema-v2#", + "name": "Web/Function Apps Slot Hybrid Connection Relay", + "summary": "This module deploys a Site Slot Hybrid Connection Namespace Relay.", + "owner": "Azure/module-maintainers" +} diff --git a/modules/web/sites/slots/hybrid-connection-namespaces/relays/version.json b/modules/web/sites/slots/hybrid-connection-namespaces/relays/version.json new file mode 100644 index 0000000000..039073633f --- /dev/null +++ b/modules/web/sites/slots/hybrid-connection-namespaces/relays/version.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://aka.ms/bicep-registry-module-version-file-schema#", + "version": "0.1", + "pathFilters": [ + "./main.json", + "./metadata.json" + ] +} diff --git a/modules/web/sites/slots/main.bicep b/modules/web/sites/slots/main.bicep index b39e6fcec9..f47aa54b19 100644 --- a/modules/web/sites/slots/main.bicep +++ b/modules/web/sites/slots/main.bicep @@ -207,6 +207,9 @@ param vnetImagePullEnabled bool = false @description('Optional. Virtual Network Route All enabled. This causes all outbound traffic to have Virtual Network Security Groups and User Defined Routes applied.') param vnetRouteAllEnabled bool = false +@description('Optional. Names of hybrid connection relays to connect app with.') +param hybridConnectionRelays array = [] + // =========== // // Variables // // =========== // @@ -321,6 +324,17 @@ module slot_authsettingsv2 'config--authsettingsv2/main.bicep' = if (!empty(auth } } +module slot_hybridConnectionRelays 'hybrid-connection-namespaces/relays/main.bicep' = [for (hybridConnectionRelay, index) in hybridConnectionRelays: { + name: '${uniqueString(deployment().name, location)}-Slot-HybridConnectionRelay-${index}' + params: { + hybridConnectionResourceId: hybridConnectionRelay.resourceId + appName: app.name + slotName: slot.name + sendKeyName: contains(hybridConnectionRelay, 'sendKeyName') ? hybridConnectionRelay.sendKeyName : null + enableDefaultTelemetry: enableReferencedModulesTelemetry + } +}] + resource slot_lock 'Microsoft.Authorization/locks@2017-04-01' = if (!empty(lock)) { name: '${slot.name}-${lock}-lock' properties: {