diff --git a/modules/app-configuration/configuration-store/.test/pe/main.test.bicep b/modules/app-configuration/configuration-store/.test/pe/main.test.bicep index 967fb336b2..8b5d2ee82c 100644 --- a/modules/app-configuration/configuration-store/.test/pe/main.test.bicep +++ b/modules/app-configuration/configuration-store/.test/pe/main.test.bicep @@ -59,7 +59,6 @@ module testDeployment '../../main.bicep' = { nestedDependencies.outputs.privateDNSZoneResourceId ] - service: 'configurationStores' subnetResourceId: nestedDependencies.outputs.subnetResourceId tags: { 'hidden-title': 'This is visible in the resource name' diff --git a/modules/app-configuration/configuration-store/README.md b/modules/app-configuration/configuration-store/README.md index cb2b945d6f..cc0ac05199 100644 --- a/modules/app-configuration/configuration-store/README.md +++ b/modules/app-configuration/configuration-store/README.md @@ -404,7 +404,6 @@ module configurationStore 'br:bicep/modules/app-configuration.configuration-stor privateDnsZoneResourceIds: [ '' ] - service: 'configurationStores' subnetResourceId: '' tags: { Environment: 'Non-Prod' @@ -458,7 +457,6 @@ module configurationStore 'br:bicep/modules/app-configuration.configuration-stor "privateDnsZoneResourceIds": [ "" ], - "service": "configurationStores", "subnetResourceId": "", "tags": { "Environment": "Non-Prod", @@ -690,7 +688,168 @@ Name of the Azure App Configuration. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible. - Required: No - Type: array -- Default: `[]` + + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`applicationSecurityGroupResourceIds`](#parameter-privateendpointsapplicationsecuritygroupresourceids) | No | array | Optional. Application security groups in which the private endpoint IP configuration is included. | +| [`customDnsConfigs`](#parameter-privateendpointscustomdnsconfigs) | No | array | Optional. Custom DNS configurations. | +| [`customNetworkInterfaceName`](#parameter-privateendpointscustomnetworkinterfacename) | No | string | Optional. The custom name of the network interface attached to the private endpoint. | +| [`enableTelemetry`](#parameter-privateendpointsenabletelemetry) | No | bool | Optional. Enable/Disable usage telemetry for module. | +| [`ipConfigurations`](#parameter-privateendpointsipconfigurations) | No | array | Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints. | +| [`location`](#parameter-privateendpointslocation) | No | string | Optional. The location to deploy the private endpoint to. | +| [`lock`](#parameter-privateendpointslock) | No | object | Optional. Specify the type of lock. | +| [`manualPrivateLinkServiceConnections`](#parameter-privateendpointsmanualprivatelinkserviceconnections) | No | array | Optional. Manual PrivateLink Service Connections. | +| [`name`](#parameter-privateendpointsname) | No | string | Optional. The name of the private endpoint. | +| [`privateDnsZoneGroupName`](#parameter-privateendpointsprivatednszonegroupname) | No | string | Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided. | +| [`privateDnsZoneResourceIds`](#parameter-privateendpointsprivatednszoneresourceids) | No | array | Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones. | +| [`roleAssignments`](#parameter-privateendpointsroleassignments) | No | array | 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'. | +| [`service`](#parameter-privateendpointsservice) | No | string | Optional. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob". | +| [`subnetResourceId`](#parameter-privateendpointssubnetresourceid) | Yes | string | Required. Resource ID of the subnet where the endpoint needs to be created. | +| [`tags`](#parameter-privateendpointstags) | No | object | Optional. Tags to be applied on all resources/resource groups in this deployment. | + +### Parameter: `privateEndpoints.applicationSecurityGroupResourceIds` + +Optional. Application security groups in which the private endpoint IP configuration is included. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.customDnsConfigs` + +Optional. Custom DNS configurations. + +- Required: No +- Type: array + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`fqdn`](#parameter-privateendpointscustomdnsconfigsfqdn) | No | string | | +| [`ipAddresses`](#parameter-privateendpointscustomdnsconfigsipaddresses) | Yes | array | | + +### Parameter: `privateEndpoints.customDnsConfigs.fqdn` +- Required: No +- Type: string + +### Parameter: `privateEndpoints.customDnsConfigs.ipAddresses` +- Required: Yes +- Type: array + + +### Parameter: `privateEndpoints.customNetworkInterfaceName` + +Optional. The custom name of the network interface attached to the private endpoint. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.enableTelemetry` + +Optional. Enable/Disable usage telemetry for module. + +- Required: No +- Type: bool + +### Parameter: `privateEndpoints.ipConfigurations` + +Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints. + +- Required: No +- Type: array + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`groupId`](#parameter-privateendpointsipconfigurationsgroupid) | Yes | string | | +| [`memberName`](#parameter-privateendpointsipconfigurationsmembername) | Yes | string | | +| [`name`](#parameter-privateendpointsipconfigurationsname) | Yes | string | | +| [`privateIpAddress`](#parameter-privateendpointsipconfigurationsprivateipaddress) | Yes | string | | + +### Parameter: `privateEndpoints.ipConfigurations.groupId` +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.ipConfigurations.memberName` +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.ipConfigurations.name` +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.ipConfigurations.privateIpAddress` +- Required: Yes +- Type: string + + +### Parameter: `privateEndpoints.location` + +Optional. The location to deploy the private endpoint to. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.lock` + +Optional. Specify the type of lock. + +- Required: No +- Type: object + +### Parameter: `privateEndpoints.manualPrivateLinkServiceConnections` + +Optional. Manual PrivateLink Service Connections. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.name` + +Optional. The name of the private endpoint. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.privateDnsZoneGroupName` + +Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.privateDnsZoneResourceIds` + +Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.roleAssignments` + +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'. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.service` + +Optional. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob". + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.subnetResourceId` + +Required. Resource ID of the subnet where the endpoint needs to be created. + +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.tags` + +Optional. Tags to be applied on all resources/resource groups in this deployment. + +- Required: No +- Type: object ### Parameter: `publicNetworkAccess` diff --git a/modules/app-configuration/configuration-store/main.bicep b/modules/app-configuration/configuration-store/main.bicep index 2853afd817..e3cb9d897f 100644 --- a/modules/app-configuration/configuration-store/main.bicep +++ b/modules/app-configuration/configuration-store/main.bicep @@ -109,7 +109,7 @@ param diagnosticMetricsToEnable array = [ param diagnosticSettingsName string = '' @description('Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible.') -param privateEndpoints array = [] +param privateEndpoints privateEndpointType var enableReferencedModulesTelemetry = false @@ -247,27 +247,27 @@ resource configurationStore_roleAssignments 'Microsoft.Authorization/roleAssignm scope: configurationStore }] -module configurationStore_privateEndpoints '../../network/private-endpoint/main.bicep' = [for (privateEndpoint, index) in privateEndpoints: { - name: '${uniqueString(deployment().name, location)}-AppConfig-PrivateEndpoint-${index}' +module configurationStore_privateEndpoints '../../network/private-endpoint/main.bicep' = [for (privateEndpoint, index) in (privateEndpoints ?? []): { + name: '${uniqueString(deployment().name, location)}-configurationStore-PrivateEndpoint-${index}' params: { groupIds: [ - privateEndpoint.service + privateEndpoint.?service ?? 'configurationStores' ] - name: contains(privateEndpoint, 'name') ? privateEndpoint.name : 'pe-${last(split(configurationStore.id, '/'))}-${privateEndpoint.service}-${index}' + name: privateEndpoint.?name ?? 'pep-${last(split(configurationStore.id, '/'))}-${privateEndpoint.?service ?? 'configurationStores'}-${index}' serviceResourceId: configurationStore.id subnetResourceId: privateEndpoint.subnetResourceId - enableDefaultTelemetry: enableReferencedModulesTelemetry - location: contains(privateEndpoint, 'location') ? privateEndpoint.location : reference(split(privateEndpoint.subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location + enableDefaultTelemetry: privateEndpoint.?enableDefaultTelemetry ?? enableReferencedModulesTelemetry + location: privateEndpoint.?location ?? reference(split(privateEndpoint.subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location lock: privateEndpoint.?lock ?? lock - privateDnsZoneGroupName: contains(privateEndpoint, 'privateDnsZoneGroupName') ? privateEndpoint.privateDnsZoneGroupName : 'default' - privateDnsZoneResourceIds: contains(privateEndpoint, 'privateDnsZoneResourceIds') ? privateEndpoint.privateDnsZoneResourceIds : [] - 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 : [] - applicationSecurityGroupResourceIds: contains(privateEndpoint, 'applicationSecurityGroupResourceIds') ? privateEndpoint.applicationSecurityGroupResourceIds : [] - customNetworkInterfaceName: contains(privateEndpoint, 'customNetworkInterfaceName') ? privateEndpoint.customNetworkInterfaceName : '' + privateDnsZoneGroupName: privateEndpoint.?privateDnsZoneGroupName + privateDnsZoneResourceIds: privateEndpoint.?privateDnsZoneResourceIds + roleAssignments: privateEndpoint.?roleAssignments + tags: privateEndpoint.?tags ?? tags + manualPrivateLinkServiceConnections: privateEndpoint.?manualPrivateLinkServiceConnections + customDnsConfigs: privateEndpoint.?customDnsConfigs + ipConfigurations: privateEndpoint.?ipConfigurations + applicationSecurityGroupResourceIds: privateEndpoint.?applicationSecurityGroupResourceIds + customNetworkInterfaceName: privateEndpoint.?customNetworkInterfaceName } }] @@ -320,3 +320,58 @@ type roleAssignmentType = { @description('Optional. The Resource Id of the delegated managed identity resource.') delegatedManagedIdentityResourceId: string? }[]? + +type privateEndpointType = { + @description('Optional. The name of the private endpoint.') + name: string? + + @description('Optional. The location to deploy the private endpoint to.') + location: string? + + @description('Optional. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob".') + service: string? + + @description('Required. Resource ID of the subnet where the endpoint needs to be created.') + subnetResourceId: string + + @description('Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided.') + privateDnsZoneGroupName: string? + + @description('Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones.') + privateDnsZoneResourceIds: string[]? + + @description('Optional. Custom DNS configurations.') + customDnsConfigs: { + fqdn: string? + ipAddresses: string[] + }[]? + + @description('Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints.') + ipConfigurations: { + name: string + groupId: string + memberName: string + privateIpAddress: string + }[]? + + @description('Optional. Application security groups in which the private endpoint IP configuration is included.') + applicationSecurityGroupResourceIds: string[]? + + @description('Optional. The custom name of the network interface attached to the private endpoint.') + customNetworkInterfaceName: string? + + @description('Optional. Specify the type of lock.') + lock: lockType + + @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\'.') + roleAssignments: roleAssignmentType + + @description('Optional. Tags to be applied on all resources/resource groups in this deployment.') + tags: object? + + @description('Optional. Manual PrivateLink Service Connections.') + manualPrivateLinkServiceConnections: array? + + @description('Optional. Enable/Disable usage telemetry for module.') + enableTelemetry: bool? +}[]? diff --git a/modules/app-configuration/configuration-store/main.json b/modules/app-configuration/configuration-store/main.json index b33ac571a2..1939f9e922 100644 --- a/modules/app-configuration/configuration-store/main.json +++ b/modules/app-configuration/configuration-store/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.22.6.54827", - "templateHash": "6369795198823213489" + "templateHash": "9341270782122671710" }, "name": "App Configuration Stores", "description": "This module deploys an App Configuration Store.", @@ -103,6 +103,155 @@ } }, "nullable": true + }, + "privateEndpointType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private endpoint." + } + }, + "location": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The location to deploy the private endpoint to." + } + }, + "service": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The service (sub-) type to deploy the private endpoint for. For example \"vault\" or \"blob\"." + } + }, + "subnetResourceId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the subnet where the endpoint needs to be created." + } + }, + "privateDnsZoneGroupName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided." + } + }, + "privateDnsZoneResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones." + } + }, + "customDnsConfigs": { + "type": "array", + "items": { + "type": "object", + "properties": { + "fqdn": { + "type": "string", + "nullable": true + }, + "ipAddresses": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. Custom DNS configurations." + } + }, + "ipConfigurations": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "groupId": { + "type": "string" + }, + "memberName": { + "type": "string" + }, + "privateIpAddress": { + "type": "string" + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints." + } + }, + "applicationSecurityGroupResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. Application security groups in which the private endpoint IP configuration is included." + } + }, + "customNetworkInterfaceName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The custom name of the network interface attached to the private endpoint." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "metadata": { + "description": "Optional. Specify the type of lock." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "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'." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags to be applied on all resources/resource groups in this deployment." + } + }, + "manualPrivateLinkServiceConnections": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. Manual PrivateLink Service Connections." + } + }, + "enableTelemetry": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + } + } + }, + "nullable": true } }, "parameters": { @@ -314,8 +463,7 @@ } }, "privateEndpoints": { - "type": "array", - "defaultValue": [], + "$ref": "#/definitions/privateEndpointType", "metadata": { "description": "Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible." } @@ -623,11 +771,11 @@ "configurationStore_privateEndpoints": { "copy": { "name": "configurationStore_privateEndpoints", - "count": "[length(parameters('privateEndpoints'))]" + "count": "[length(coalesce(parameters('privateEndpoints'), createArray()))]" }, "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", - "name": "[format('{0}-AppConfig-PrivateEndpoint-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "name": "[format('{0}-configurationStore-PrivateEndpoint-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", "properties": { "expressionEvaluationOptions": { "scope": "inner" @@ -636,32 +784,54 @@ "parameters": { "groupIds": { "value": [ - "[parameters('privateEndpoints')[copyIndex()].service]" + "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'configurationStores')]" ] }, - "name": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'name'), createObject('value', parameters('privateEndpoints')[copyIndex()].name), createObject('value', format('pe-{0}-{1}-{2}', last(split(resourceId('Microsoft.AppConfiguration/configurationStores', parameters('name')), '/')), parameters('privateEndpoints')[copyIndex()].service, copyIndex())))]", + "name": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'name'), format('pep-{0}-{1}-{2}', last(split(resourceId('Microsoft.AppConfiguration/configurationStores', parameters('name')), '/')), coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'configurationStores'), copyIndex()))]" + }, "serviceResourceId": { "value": "[resourceId('Microsoft.AppConfiguration/configurationStores', parameters('name'))]" }, "subnetResourceId": { - "value": "[parameters('privateEndpoints')[copyIndex()].subnetResourceId]" + "value": "[coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId]" }, "enableDefaultTelemetry": { - "value": "[variables('enableReferencedModulesTelemetry')]" + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'enableDefaultTelemetry'), variables('enableReferencedModulesTelemetry'))]" + }, + "location": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'location'), reference(split(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location)]" }, - "location": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'location'), createObject('value', parameters('privateEndpoints')[copyIndex()].location), createObject('value', reference(split(parameters('privateEndpoints')[copyIndex()].subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location))]", "lock": { - "value": "[coalesce(tryGet(parameters('privateEndpoints')[copyIndex()], 'lock'), parameters('lock'))]" + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'lock'), parameters('lock'))]" + }, + "privateDnsZoneGroupName": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneGroupName')]" + }, + "privateDnsZoneResourceIds": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneResourceIds')]" + }, + "roleAssignments": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'roleAssignments')]" }, - "privateDnsZoneGroupName": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'privateDnsZoneGroupName'), createObject('value', parameters('privateEndpoints')[copyIndex()].privateDnsZoneGroupName), createObject('value', 'default'))]", - "privateDnsZoneResourceIds": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'privateDnsZoneResourceIds'), createObject('value', parameters('privateEndpoints')[copyIndex()].privateDnsZoneResourceIds), createObject('value', createArray()))]", - "roleAssignments": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'roleAssignments'), createObject('value', parameters('privateEndpoints')[copyIndex()].roleAssignments), createObject('value', createArray()))]", - "tags": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'tags'), createObject('value', parameters('privateEndpoints')[copyIndex()].tags), createObject('value', createObject()))]", - "manualPrivateLinkServiceConnections": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'manualPrivateLinkServiceConnections'), createObject('value', parameters('privateEndpoints')[copyIndex()].manualPrivateLinkServiceConnections), createObject('value', createArray()))]", - "customDnsConfigs": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'customDnsConfigs'), createObject('value', parameters('privateEndpoints')[copyIndex()].customDnsConfigs), createObject('value', createArray()))]", - "ipConfigurations": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'ipConfigurations'), createObject('value', parameters('privateEndpoints')[copyIndex()].ipConfigurations), createObject('value', createArray()))]", - "applicationSecurityGroupResourceIds": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'applicationSecurityGroupResourceIds'), createObject('value', parameters('privateEndpoints')[copyIndex()].applicationSecurityGroupResourceIds), createObject('value', createArray()))]", - "customNetworkInterfaceName": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'customNetworkInterfaceName'), createObject('value', parameters('privateEndpoints')[copyIndex()].customNetworkInterfaceName), createObject('value', ''))]" + "tags": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'tags'), parameters('tags'))]" + }, + "manualPrivateLinkServiceConnections": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'manualPrivateLinkServiceConnections')]" + }, + "customDnsConfigs": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customDnsConfigs')]" + }, + "ipConfigurations": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'ipConfigurations')]" + }, + "applicationSecurityGroupResourceIds": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'applicationSecurityGroupResourceIds')]" + }, + "customNetworkInterfaceName": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customNetworkInterfaceName')]" + } }, "template": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", diff --git a/modules/automation/automation-account/README.md b/modules/automation/automation-account/README.md index 1930cea49f..c68194e8d3 100644 --- a/modules/automation/automation-account/README.md +++ b/modules/automation/automation-account/README.md @@ -809,7 +809,168 @@ Name of the Automation Account. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible. - Required: No - Type: array -- Default: `[]` + + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`applicationSecurityGroupResourceIds`](#parameter-privateendpointsapplicationsecuritygroupresourceids) | No | array | Optional. Application security groups in which the private endpoint IP configuration is included. | +| [`customDnsConfigs`](#parameter-privateendpointscustomdnsconfigs) | No | array | Optional. Custom DNS configurations. | +| [`customNetworkInterfaceName`](#parameter-privateendpointscustomnetworkinterfacename) | No | string | Optional. The custom name of the network interface attached to the private endpoint. | +| [`enableTelemetry`](#parameter-privateendpointsenabletelemetry) | No | bool | Optional. Enable/Disable usage telemetry for module. | +| [`ipConfigurations`](#parameter-privateendpointsipconfigurations) | No | array | Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints. | +| [`location`](#parameter-privateendpointslocation) | No | string | Optional. The location to deploy the private endpoint to. | +| [`lock`](#parameter-privateendpointslock) | No | object | Optional. Specify the type of lock. | +| [`manualPrivateLinkServiceConnections`](#parameter-privateendpointsmanualprivatelinkserviceconnections) | No | array | Optional. Manual PrivateLink Service Connections. | +| [`name`](#parameter-privateendpointsname) | No | string | Optional. The name of the private endpoint. | +| [`privateDnsZoneGroupName`](#parameter-privateendpointsprivatednszonegroupname) | No | string | Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided. | +| [`privateDnsZoneResourceIds`](#parameter-privateendpointsprivatednszoneresourceids) | No | array | Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones. | +| [`roleAssignments`](#parameter-privateendpointsroleassignments) | No | array | 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'. | +| [`service`](#parameter-privateendpointsservice) | Yes | string | Required. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob". | +| [`subnetResourceId`](#parameter-privateendpointssubnetresourceid) | Yes | string | Required. Resource ID of the subnet where the endpoint needs to be created. | +| [`tags`](#parameter-privateendpointstags) | No | object | Optional. Tags to be applied on all resources/resource groups in this deployment. | + +### Parameter: `privateEndpoints.applicationSecurityGroupResourceIds` + +Optional. Application security groups in which the private endpoint IP configuration is included. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.customDnsConfigs` + +Optional. Custom DNS configurations. + +- Required: No +- Type: array + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`fqdn`](#parameter-privateendpointscustomdnsconfigsfqdn) | No | string | | +| [`ipAddresses`](#parameter-privateendpointscustomdnsconfigsipaddresses) | Yes | array | | + +### Parameter: `privateEndpoints.customDnsConfigs.fqdn` +- Required: No +- Type: string + +### Parameter: `privateEndpoints.customDnsConfigs.ipAddresses` +- Required: Yes +- Type: array + + +### Parameter: `privateEndpoints.customNetworkInterfaceName` + +Optional. The custom name of the network interface attached to the private endpoint. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.enableTelemetry` + +Optional. Enable/Disable usage telemetry for module. + +- Required: No +- Type: bool + +### Parameter: `privateEndpoints.ipConfigurations` + +Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints. + +- Required: No +- Type: array + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`groupId`](#parameter-privateendpointsipconfigurationsgroupid) | Yes | string | | +| [`memberName`](#parameter-privateendpointsipconfigurationsmembername) | Yes | string | | +| [`name`](#parameter-privateendpointsipconfigurationsname) | Yes | string | | +| [`privateIpAddress`](#parameter-privateendpointsipconfigurationsprivateipaddress) | Yes | string | | + +### Parameter: `privateEndpoints.ipConfigurations.groupId` +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.ipConfigurations.memberName` +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.ipConfigurations.name` +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.ipConfigurations.privateIpAddress` +- Required: Yes +- Type: string + + +### Parameter: `privateEndpoints.location` + +Optional. The location to deploy the private endpoint to. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.lock` + +Optional. Specify the type of lock. + +- Required: No +- Type: object + +### Parameter: `privateEndpoints.manualPrivateLinkServiceConnections` + +Optional. Manual PrivateLink Service Connections. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.name` + +Optional. The name of the private endpoint. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.privateDnsZoneGroupName` + +Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.privateDnsZoneResourceIds` + +Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.roleAssignments` + +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'. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.service` + +Required. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob". + +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.subnetResourceId` + +Required. Resource ID of the subnet where the endpoint needs to be created. + +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.tags` + +Optional. Tags to be applied on all resources/resource groups in this deployment. + +- Required: No +- Type: object ### Parameter: `publicNetworkAccess` diff --git a/modules/automation/automation-account/main.bicep b/modules/automation/automation-account/main.bicep index 6afbd479a3..1534d5e0c9 100644 --- a/modules/automation/automation-account/main.bicep +++ b/modules/automation/automation-account/main.bicep @@ -63,7 +63,7 @@ param publicNetworkAccess string = '' param disableLocalAuth bool = true @description('Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible.') -param privateEndpoints array = [] +param privateEndpoints privateEndpointType @description('Optional. Resource ID of the diagnostic storage account.') param diagnosticStorageAccountId string = '' @@ -376,27 +376,27 @@ resource automationAccount_diagnosticSettings 'Microsoft.Insights/diagnosticSett scope: automationAccount } -module automationAccount_privateEndpoints '../../network/private-endpoint/main.bicep' = [for (privateEndpoint, index) in privateEndpoints: { - name: '${uniqueString(deployment().name, location)}-AutomationAccount-PrivateEndpoint-${index}' +module automationAccount_privateEndpoints '../../network/private-endpoint/main.bicep' = [for (privateEndpoint, index) in (privateEndpoints ?? []): { + name: '${uniqueString(deployment().name, location)}-automationAccount-PrivateEndpoint-${index}' params: { groupIds: [ privateEndpoint.service ] - name: contains(privateEndpoint, 'name') ? privateEndpoint.name : 'pe-${last(split(automationAccount.id, '/'))}-${privateEndpoint.service}-${index}' + name: privateEndpoint.?name ?? 'pep-${last(split(automationAccount.id, '/'))}-${privateEndpoint.?service ?? privateEndpoint.service}-${index}' serviceResourceId: automationAccount.id subnetResourceId: privateEndpoint.subnetResourceId - enableDefaultTelemetry: enableReferencedModulesTelemetry - location: contains(privateEndpoint, 'location') ? privateEndpoint.location : reference(split(privateEndpoint.subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location + enableDefaultTelemetry: privateEndpoint.?enableDefaultTelemetry ?? enableReferencedModulesTelemetry + location: privateEndpoint.?location ?? reference(split(privateEndpoint.subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location lock: privateEndpoint.?lock ?? lock - privateDnsZoneGroupName: contains(privateEndpoint, 'privateDnsZoneGroupName') ? privateEndpoint.privateDnsZoneGroupName : 'default' - privateDnsZoneResourceIds: contains(privateEndpoint, 'privateDnsZoneResourceIds') ? privateEndpoint.privateDnsZoneResourceIds : [] - 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 : [] - applicationSecurityGroupResourceIds: contains(privateEndpoint, 'applicationSecurityGroupResourceIds') ? privateEndpoint.applicationSecurityGroupResourceIds : [] - customNetworkInterfaceName: contains(privateEndpoint, 'customNetworkInterfaceName') ? privateEndpoint.customNetworkInterfaceName : '' + privateDnsZoneGroupName: privateEndpoint.?privateDnsZoneGroupName + privateDnsZoneResourceIds: privateEndpoint.?privateDnsZoneResourceIds + roleAssignments: privateEndpoint.?roleAssignments + tags: privateEndpoint.?tags ?? tags + manualPrivateLinkServiceConnections: privateEndpoint.?manualPrivateLinkServiceConnections + customDnsConfigs: privateEndpoint.?customDnsConfigs + ipConfigurations: privateEndpoint.?ipConfigurations + applicationSecurityGroupResourceIds: privateEndpoint.?applicationSecurityGroupResourceIds + customNetworkInterfaceName: privateEndpoint.?customNetworkInterfaceName } }] @@ -463,3 +463,58 @@ type roleAssignmentType = { @description('Optional. The Resource Id of the delegated managed identity resource.') delegatedManagedIdentityResourceId: string? }[]? + +type privateEndpointType = { + @description('Optional. The name of the private endpoint.') + name: string? + + @description('Optional. The location to deploy the private endpoint to.') + location: string? + + @description('Required. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob".') + service: string + + @description('Required. Resource ID of the subnet where the endpoint needs to be created.') + subnetResourceId: string + + @description('Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided.') + privateDnsZoneGroupName: string? + + @description('Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones.') + privateDnsZoneResourceIds: string[]? + + @description('Optional. Custom DNS configurations.') + customDnsConfigs: { + fqdn: string? + ipAddresses: string[] + }[]? + + @description('Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints.') + ipConfigurations: { + name: string + groupId: string + memberName: string + privateIpAddress: string + }[]? + + @description('Optional. Application security groups in which the private endpoint IP configuration is included.') + applicationSecurityGroupResourceIds: string[]? + + @description('Optional. The custom name of the network interface attached to the private endpoint.') + customNetworkInterfaceName: string? + + @description('Optional. Specify the type of lock.') + lock: lockType + + @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\'.') + roleAssignments: roleAssignmentType + + @description('Optional. Tags to be applied on all resources/resource groups in this deployment.') + tags: object? + + @description('Optional. Manual PrivateLink Service Connections.') + manualPrivateLinkServiceConnections: array? + + @description('Optional. Enable/Disable usage telemetry for module.') + enableTelemetry: bool? +}[]? diff --git a/modules/automation/automation-account/main.json b/modules/automation/automation-account/main.json index 985e446999..b148064e91 100644 --- a/modules/automation/automation-account/main.json +++ b/modules/automation/automation-account/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.22.6.54827", - "templateHash": "3326115311371302534" + "templateHash": "17662801875891298684" }, "name": "Automation Accounts", "description": "This module deploys an Azure Automation Account.", @@ -103,6 +103,154 @@ } }, "nullable": true + }, + "privateEndpointType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private endpoint." + } + }, + "location": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The location to deploy the private endpoint to." + } + }, + "service": { + "type": "string", + "metadata": { + "description": "Required. The service (sub-) type to deploy the private endpoint for. For example \"vault\" or \"blob\"." + } + }, + "subnetResourceId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the subnet where the endpoint needs to be created." + } + }, + "privateDnsZoneGroupName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided." + } + }, + "privateDnsZoneResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones." + } + }, + "customDnsConfigs": { + "type": "array", + "items": { + "type": "object", + "properties": { + "fqdn": { + "type": "string", + "nullable": true + }, + "ipAddresses": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. Custom DNS configurations." + } + }, + "ipConfigurations": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "groupId": { + "type": "string" + }, + "memberName": { + "type": "string" + }, + "privateIpAddress": { + "type": "string" + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints." + } + }, + "applicationSecurityGroupResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. Application security groups in which the private endpoint IP configuration is included." + } + }, + "customNetworkInterfaceName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The custom name of the network interface attached to the private endpoint." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "metadata": { + "description": "Optional. Specify the type of lock." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "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'." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags to be applied on all resources/resource groups in this deployment." + } + }, + "manualPrivateLinkServiceConnections": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. Manual PrivateLink Service Connections." + } + }, + "enableTelemetry": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + } + } + }, + "nullable": true } }, "parameters": { @@ -234,8 +382,7 @@ } }, "privateEndpoints": { - "type": "array", - "defaultValue": [], + "$ref": "#/definitions/privateEndpointType", "metadata": { "description": "Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible." } @@ -2136,11 +2283,11 @@ "automationAccount_privateEndpoints": { "copy": { "name": "automationAccount_privateEndpoints", - "count": "[length(parameters('privateEndpoints'))]" + "count": "[length(coalesce(parameters('privateEndpoints'), createArray()))]" }, "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", - "name": "[format('{0}-AutomationAccount-PrivateEndpoint-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "name": "[format('{0}-automationAccount-PrivateEndpoint-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", "properties": { "expressionEvaluationOptions": { "scope": "inner" @@ -2149,32 +2296,54 @@ "parameters": { "groupIds": { "value": [ - "[parameters('privateEndpoints')[copyIndex()].service]" + "[coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].service]" ] }, - "name": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'name'), createObject('value', parameters('privateEndpoints')[copyIndex()].name), createObject('value', format('pe-{0}-{1}-{2}', last(split(resourceId('Microsoft.Automation/automationAccounts', parameters('name')), '/')), parameters('privateEndpoints')[copyIndex()].service, copyIndex())))]", + "name": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'name'), format('pep-{0}-{1}-{2}', last(split(resourceId('Microsoft.Automation/automationAccounts', parameters('name')), '/')), coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].service), copyIndex()))]" + }, "serviceResourceId": { "value": "[resourceId('Microsoft.Automation/automationAccounts', parameters('name'))]" }, "subnetResourceId": { - "value": "[parameters('privateEndpoints')[copyIndex()].subnetResourceId]" + "value": "[coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId]" }, "enableDefaultTelemetry": { - "value": "[variables('enableReferencedModulesTelemetry')]" + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'enableDefaultTelemetry'), variables('enableReferencedModulesTelemetry'))]" + }, + "location": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'location'), reference(split(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location)]" }, - "location": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'location'), createObject('value', parameters('privateEndpoints')[copyIndex()].location), createObject('value', reference(split(parameters('privateEndpoints')[copyIndex()].subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location))]", "lock": { - "value": "[coalesce(tryGet(parameters('privateEndpoints')[copyIndex()], 'lock'), parameters('lock'))]" - }, - "privateDnsZoneGroupName": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'privateDnsZoneGroupName'), createObject('value', parameters('privateEndpoints')[copyIndex()].privateDnsZoneGroupName), createObject('value', 'default'))]", - "privateDnsZoneResourceIds": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'privateDnsZoneResourceIds'), createObject('value', parameters('privateEndpoints')[copyIndex()].privateDnsZoneResourceIds), createObject('value', createArray()))]", - "roleAssignments": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'roleAssignments'), createObject('value', parameters('privateEndpoints')[copyIndex()].roleAssignments), createObject('value', createArray()))]", - "tags": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'tags'), createObject('value', parameters('privateEndpoints')[copyIndex()].tags), createObject('value', createObject()))]", - "manualPrivateLinkServiceConnections": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'manualPrivateLinkServiceConnections'), createObject('value', parameters('privateEndpoints')[copyIndex()].manualPrivateLinkServiceConnections), createObject('value', createArray()))]", - "customDnsConfigs": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'customDnsConfigs'), createObject('value', parameters('privateEndpoints')[copyIndex()].customDnsConfigs), createObject('value', createArray()))]", - "ipConfigurations": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'ipConfigurations'), createObject('value', parameters('privateEndpoints')[copyIndex()].ipConfigurations), createObject('value', createArray()))]", - "applicationSecurityGroupResourceIds": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'applicationSecurityGroupResourceIds'), createObject('value', parameters('privateEndpoints')[copyIndex()].applicationSecurityGroupResourceIds), createObject('value', createArray()))]", - "customNetworkInterfaceName": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'customNetworkInterfaceName'), createObject('value', parameters('privateEndpoints')[copyIndex()].customNetworkInterfaceName), createObject('value', ''))]" + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'lock'), parameters('lock'))]" + }, + "privateDnsZoneGroupName": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneGroupName')]" + }, + "privateDnsZoneResourceIds": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneResourceIds')]" + }, + "roleAssignments": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'roleAssignments')]" + }, + "tags": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'tags'), parameters('tags'))]" + }, + "manualPrivateLinkServiceConnections": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'manualPrivateLinkServiceConnections')]" + }, + "customDnsConfigs": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customDnsConfigs')]" + }, + "ipConfigurations": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'ipConfigurations')]" + }, + "applicationSecurityGroupResourceIds": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'applicationSecurityGroupResourceIds')]" + }, + "customNetworkInterfaceName": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customNetworkInterfaceName')]" + } }, "template": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", diff --git a/modules/batch/batch-account/.test/common/main.test.bicep b/modules/batch/batch-account/.test/common/main.test.bicep index f579e79863..e7fbf8557e 100644 --- a/modules/batch/batch-account/.test/common/main.test.bicep +++ b/modules/batch/batch-account/.test/common/main.test.bicep @@ -78,9 +78,15 @@ module testDeployment '../../main.bicep' = { name: 'myCustomLockName' } poolAllocationMode: 'BatchService' + roleAssignments: [ + { + roleDefinitionIdOrName: 'Reader' + principalId: nestedDependencies.outputs.managedIdentityPrincipalId + principalType: 'ServicePrincipal' + } + ] privateEndpoints: [ { - service: 'batchAccount' subnetResourceId: nestedDependencies.outputs.subnetResourceId privateDnsZoneResourceIds: [ nestedDependencies.outputs.privateDNSZoneResourceId diff --git a/modules/batch/batch-account/README.md b/modules/batch/batch-account/README.md index 74a18e3afd..f6b69b8e94 100644 --- a/modules/batch/batch-account/README.md +++ b/modules/batch/batch-account/README.md @@ -72,7 +72,6 @@ module batchAccount 'br:bicep/modules/batch.batch-account:1.0.0' = { roleDefinitionIdOrName: 'Reader' } ] - service: 'batchAccount' subnetResourceId: '' tags: { Environment: 'Non-Prod' @@ -81,6 +80,13 @@ module batchAccount 'br:bicep/modules/batch.batch-account:1.0.0' = { } } ] + roleAssignments: [ + { + principalId: '' + principalType: 'ServicePrincipal' + roleDefinitionIdOrName: 'Reader' + } + ] storageAccessIdentity: '' storageAuthenticationMode: 'BatchAccountManagedIdentity' systemAssignedIdentity: true @@ -150,7 +156,6 @@ module batchAccount 'br:bicep/modules/batch.batch-account:1.0.0' = { "roleDefinitionIdOrName": "Reader" } ], - "service": "batchAccount", "subnetResourceId": "", "tags": { "Environment": "Non-Prod", @@ -160,6 +165,15 @@ module batchAccount 'br:bicep/modules/batch.batch-account:1.0.0' = { } ] }, + "roleAssignments": { + "value": [ + { + "principalId": "", + "principalType": "ServicePrincipal", + "roleDefinitionIdOrName": "Reader" + } + ] + }, "storageAccessIdentity": { "value": "" }, @@ -393,6 +407,7 @@ module batchAccount 'br:bicep/modules/batch.batch-account:1.0.0' = { | [`poolAllocationMode`](#parameter-poolallocationmode) | string | The allocation mode for creating pools in the Batch account. Determines which quota will be used. | | [`privateEndpoints`](#parameter-privateendpoints) | array | Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible. | | [`publicNetworkAccess`](#parameter-publicnetworkaccess) | string | Whether or not public network access is allowed for this resource. For security reasons it should be disabled. If not specified, it will be disabled by default if private endpoints are set and networkProfileAllowedIpRanges are not set. | +| [`roleAssignments`](#parameter-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'. | | [`storageAccessIdentity`](#parameter-storageaccessidentity) | string | The resource ID of a user assigned identity assigned to pools which have compute nodes that need access to auto-storage. | | [`storageAuthenticationMode`](#parameter-storageauthenticationmode) | string | The authentication mode which the Batch service will use to manage the auto-storage account. | | [`systemAssignedIdentity`](#parameter-systemassignedidentity) | bool | Enables system assigned managed identity on the resource. | @@ -561,7 +576,168 @@ The allocation mode for creating pools in the Batch account. Determines which qu Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible. - Required: No - Type: array -- Default: `[]` + + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`applicationSecurityGroupResourceIds`](#parameter-privateendpointsapplicationsecuritygroupresourceids) | No | array | Optional. Application security groups in which the private endpoint IP configuration is included. | +| [`customDnsConfigs`](#parameter-privateendpointscustomdnsconfigs) | No | array | Optional. Custom DNS configurations. | +| [`customNetworkInterfaceName`](#parameter-privateendpointscustomnetworkinterfacename) | No | string | Optional. The custom name of the network interface attached to the private endpoint. | +| [`enableTelemetry`](#parameter-privateendpointsenabletelemetry) | No | bool | Optional. Enable/Disable usage telemetry for module. | +| [`ipConfigurations`](#parameter-privateendpointsipconfigurations) | No | array | Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints. | +| [`location`](#parameter-privateendpointslocation) | No | string | Optional. The location to deploy the private endpoint to. | +| [`lock`](#parameter-privateendpointslock) | No | object | Optional. Specify the type of lock. | +| [`manualPrivateLinkServiceConnections`](#parameter-privateendpointsmanualprivatelinkserviceconnections) | No | array | Optional. Manual PrivateLink Service Connections. | +| [`name`](#parameter-privateendpointsname) | No | string | Optional. The name of the private endpoint. | +| [`privateDnsZoneGroupName`](#parameter-privateendpointsprivatednszonegroupname) | No | string | Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided. | +| [`privateDnsZoneResourceIds`](#parameter-privateendpointsprivatednszoneresourceids) | No | array | Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones. | +| [`roleAssignments`](#parameter-privateendpointsroleassignments) | No | array | 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'. | +| [`service`](#parameter-privateendpointsservice) | No | string | Optional. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob". | +| [`subnetResourceId`](#parameter-privateendpointssubnetresourceid) | Yes | string | Required. Resource ID of the subnet where the endpoint needs to be created. | +| [`tags`](#parameter-privateendpointstags) | No | object | Optional. Tags to be applied on all resources/resource groups in this deployment. | + +### Parameter: `privateEndpoints.applicationSecurityGroupResourceIds` + +Optional. Application security groups in which the private endpoint IP configuration is included. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.customDnsConfigs` + +Optional. Custom DNS configurations. + +- Required: No +- Type: array + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`fqdn`](#parameter-privateendpointscustomdnsconfigsfqdn) | No | string | | +| [`ipAddresses`](#parameter-privateendpointscustomdnsconfigsipaddresses) | Yes | array | | + +### Parameter: `privateEndpoints.customDnsConfigs.fqdn` +- Required: No +- Type: string + +### Parameter: `privateEndpoints.customDnsConfigs.ipAddresses` +- Required: Yes +- Type: array + + +### Parameter: `privateEndpoints.customNetworkInterfaceName` + +Optional. The custom name of the network interface attached to the private endpoint. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.enableTelemetry` + +Optional. Enable/Disable usage telemetry for module. + +- Required: No +- Type: bool + +### Parameter: `privateEndpoints.ipConfigurations` + +Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints. + +- Required: No +- Type: array + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`groupId`](#parameter-privateendpointsipconfigurationsgroupid) | Yes | string | | +| [`memberName`](#parameter-privateendpointsipconfigurationsmembername) | Yes | string | | +| [`name`](#parameter-privateendpointsipconfigurationsname) | Yes | string | | +| [`privateIpAddress`](#parameter-privateendpointsipconfigurationsprivateipaddress) | Yes | string | | + +### Parameter: `privateEndpoints.ipConfigurations.groupId` +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.ipConfigurations.memberName` +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.ipConfigurations.name` +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.ipConfigurations.privateIpAddress` +- Required: Yes +- Type: string + + +### Parameter: `privateEndpoints.location` + +Optional. The location to deploy the private endpoint to. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.lock` + +Optional. Specify the type of lock. + +- Required: No +- Type: object + +### Parameter: `privateEndpoints.manualPrivateLinkServiceConnections` + +Optional. Manual PrivateLink Service Connections. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.name` + +Optional. The name of the private endpoint. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.privateDnsZoneGroupName` + +Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.privateDnsZoneResourceIds` + +Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.roleAssignments` + +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'. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.service` + +Optional. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob". + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.subnetResourceId` + +Required. Resource ID of the subnet where the endpoint needs to be created. + +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.tags` + +Optional. Tags to be applied on all resources/resource groups in this deployment. + +- Required: No +- Type: object ### Parameter: `publicNetworkAccess` @@ -571,6 +747,74 @@ Whether or not public network access is allowed for this resource. For security - Default: `''` - Allowed: `['', Disabled, Enabled]` +### Parameter: `roleAssignments` + +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'. +- Required: No +- Type: array + + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`condition`](#parameter-roleassignmentscondition) | No | string | 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" | +| [`conditionVersion`](#parameter-roleassignmentsconditionversion) | No | string | Optional. Version of the condition. | +| [`delegatedManagedIdentityResourceId`](#parameter-roleassignmentsdelegatedmanagedidentityresourceid) | No | string | Optional. The Resource Id of the delegated managed identity resource. | +| [`description`](#parameter-roleassignmentsdescription) | No | string | Optional. The description of the role assignment. | +| [`principalId`](#parameter-roleassignmentsprincipalid) | Yes | string | Required. The principal ID of the principal (user/group/identity) to assign the role to. | +| [`principalType`](#parameter-roleassignmentsprincipaltype) | No | string | Optional. The principal type of the assigned principal ID. | +| [`roleDefinitionIdOrName`](#parameter-roleassignmentsroledefinitionidorname) | Yes | string | Required. The name of the role to assign. If it cannot be found you can specify the role definition ID instead. | + +### Parameter: `roleAssignments.condition` + +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" + +- Required: No +- Type: string + +### Parameter: `roleAssignments.conditionVersion` + +Optional. Version of the condition. + +- Required: No +- Type: string +- Allowed: `[2.0]` + +### Parameter: `roleAssignments.delegatedManagedIdentityResourceId` + +Optional. The Resource Id of the delegated managed identity resource. + +- Required: No +- Type: string + +### Parameter: `roleAssignments.description` + +Optional. The description of the role assignment. + +- Required: No +- Type: string + +### Parameter: `roleAssignments.principalId` + +Required. The principal ID of the principal (user/group/identity) to assign the role to. + +- Required: Yes +- Type: string + +### Parameter: `roleAssignments.principalType` + +Optional. The principal type of the assigned principal ID. + +- Required: No +- Type: string +- Allowed: `[Device, ForeignGroup, Group, ServicePrincipal, User]` + +### Parameter: `roleAssignments.roleDefinitionIdOrName` + +Required. The name of the role to assign. If it cannot be found you can specify the role definition ID instead. + +- Required: Yes +- Type: string + ### Parameter: `storageAccessIdentity` The resource ID of a user assigned identity assigned to pools which have compute nodes that need access to auto-storage. diff --git a/modules/batch/batch-account/main.bicep b/modules/batch/batch-account/main.bicep index e0f720a1d2..eba075cf69 100644 --- a/modules/batch/batch-account/main.bicep +++ b/modules/batch/batch-account/main.bicep @@ -38,7 +38,7 @@ param poolAllocationMode string = 'BatchService' param keyVaultReferenceResourceId string = '' @description('Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible.') -param privateEndpoints array = [] +param privateEndpoints privateEndpointType @description('Optional. Whether or not public network access is allowed for this resource. For security reasons it should be disabled. If not specified, it will be disabled by default if private endpoints are set and networkProfileAllowedIpRanges are not set.') @allowed([ @@ -76,6 +76,9 @@ param lock lockType @description('Optional. Tags of the resource.') param tags object = {} +@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 roleAssignmentType + @allowed([ 'AAD' 'SharedKey' @@ -159,6 +162,14 @@ var autoStorageConfig = { var enableReferencedModulesTelemetry = false +var builtInRoleNames = { + Contributor: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c') + Owner: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635') + Reader: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7') + '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 defaultTelemetry 'Microsoft.Resources/deployments@2021-04-01' = if (enableDefaultTelemetry) { name: 'pid-47ed15a6-730a-4827-bcb4-0fd963ffbd82-${uniqueString(deployment().name, location)}' properties: { @@ -231,27 +242,41 @@ resource batchAccount_diagnosticSettings 'Microsoft.Insights/diagnosticsettings@ scope: batchAccount } -module batchAccount_privateEndpoints '../../network/private-endpoint/main.bicep' = [for (privateEndpoint, index) in privateEndpoints: { - name: '${uniqueString(deployment().name, location)}-BatchAccount-PrivateEndpoint-${index}' +resource batchAccount_roleAssignments 'Microsoft.Authorization/roleAssignments@2022-04-01' = [for (roleAssignment, index) in (roleAssignments ?? []): { + name: guid(batchAccount.id, roleAssignment.principalId, roleAssignment.roleDefinitionIdOrName) + properties: { + roleDefinitionId: contains(builtInRoleNames, roleAssignment.roleDefinitionIdOrName) ? builtInRoleNames[roleAssignment.roleDefinitionIdOrName] : roleAssignment.roleDefinitionIdOrName + principalId: roleAssignment.principalId + description: roleAssignment.?description + principalType: roleAssignment.?principalType + condition: roleAssignment.?condition + conditionVersion: !empty(roleAssignment.?condition) ? (roleAssignment.?conditionVersion ?? '2.0') : null // Must only be set if condtion is set + delegatedManagedIdentityResourceId: roleAssignment.?delegatedManagedIdentityResourceId + } + scope: batchAccount +}] + +module batchAccount_privateEndpoints '../../network/private-endpoint/main.bicep' = [for (privateEndpoint, index) in (privateEndpoints ?? []): { + name: '${uniqueString(deployment().name, location)}-batchAccount-PrivateEndpoint-${index}' params: { groupIds: [ - privateEndpoint.service + privateEndpoint.?service ?? 'batchAccount' ] - name: contains(privateEndpoint, 'name') ? privateEndpoint.name : 'pe-${last(split(batchAccount.id, '/'))}-${privateEndpoint.service}-${index}' + name: privateEndpoint.?name ?? 'pep-${last(split(batchAccount.id, '/'))}-${privateEndpoint.?service ?? 'batchAccount'}-${index}' serviceResourceId: batchAccount.id subnetResourceId: privateEndpoint.subnetResourceId - enableDefaultTelemetry: enableReferencedModulesTelemetry - location: contains(privateEndpoint, 'location') ? privateEndpoint.location : reference(split(privateEndpoint.subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location + enableDefaultTelemetry: privateEndpoint.?enableDefaultTelemetry ?? enableReferencedModulesTelemetry + location: privateEndpoint.?location ?? reference(split(privateEndpoint.subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location lock: privateEndpoint.?lock ?? lock - privateDnsZoneGroupName: contains(privateEndpoint, 'privateDnsZoneGroupName') ? privateEndpoint.privateDnsZoneGroupName : 'default' - privateDnsZoneResourceIds: contains(privateEndpoint, 'privateDnsZoneResourceIds') ? privateEndpoint.privateDnsZoneResourceIds : [] - 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 : [] - applicationSecurityGroupResourceIds: contains(privateEndpoint, 'applicationSecurityGroupResourceIds') ? privateEndpoint.applicationSecurityGroupResourceIds : [] - customNetworkInterfaceName: contains(privateEndpoint, 'customNetworkInterfaceName') ? privateEndpoint.customNetworkInterfaceName : '' + privateDnsZoneGroupName: privateEndpoint.?privateDnsZoneGroupName + privateDnsZoneResourceIds: privateEndpoint.?privateDnsZoneResourceIds + roleAssignments: privateEndpoint.?roleAssignments + tags: privateEndpoint.?tags ?? tags + manualPrivateLinkServiceConnections: privateEndpoint.?manualPrivateLinkServiceConnections + customDnsConfigs: privateEndpoint.?customDnsConfigs + ipConfigurations: privateEndpoint.?ipConfigurations + applicationSecurityGroupResourceIds: privateEndpoint.?applicationSecurityGroupResourceIds + customNetworkInterfaceName: privateEndpoint.?customNetworkInterfaceName } }] @@ -278,3 +303,81 @@ type lockType = { @description('Optional. Specify the type of lock.') kind: ('CanNotDelete' | 'ReadOnly' | 'None')? }? + +type roleAssignmentType = { + @description('Required. The name of the role to assign. If it cannot be found you can specify the role definition ID instead.') + roleDefinitionIdOrName: string + + @description('Required. The principal ID of the principal (user/group/identity) to assign the role to.') + principalId: string + + @description('Optional. The principal type of the assigned principal ID.') + principalType: ('ServicePrincipal' | 'Group' | 'User' | 'ForeignGroup' | 'Device' | null)? + + @description('Optional. The description of the role assignment.') + description: string? + + @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"') + condition: string? + + @description('Optional. Version of the condition.') + conditionVersion: '2.0'? + + @description('Optional. The Resource Id of the delegated managed identity resource.') + delegatedManagedIdentityResourceId: string? +}[]? + +type privateEndpointType = { + @description('Optional. The name of the private endpoint.') + name: string? + + @description('Optional. The location to deploy the private endpoint to.') + location: string? + + @description('Optional. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob".') + service: string? + + @description('Required. Resource ID of the subnet where the endpoint needs to be created.') + subnetResourceId: string + + @description('Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided.') + privateDnsZoneGroupName: string? + + @description('Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones.') + privateDnsZoneResourceIds: string[]? + + @description('Optional. Custom DNS configurations.') + customDnsConfigs: { + fqdn: string? + ipAddresses: string[] + }[]? + + @description('Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints.') + ipConfigurations: { + name: string + groupId: string + memberName: string + privateIpAddress: string + }[]? + + @description('Optional. Application security groups in which the private endpoint IP configuration is included.') + applicationSecurityGroupResourceIds: string[]? + + @description('Optional. The custom name of the network interface attached to the private endpoint.') + customNetworkInterfaceName: string? + + @description('Optional. Specify the type of lock.') + lock: lockType + + @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\'.') + roleAssignments: roleAssignmentType + + @description('Optional. Tags to be applied on all resources/resource groups in this deployment.') + tags: object? + + @description('Optional. Manual PrivateLink Service Connections.') + manualPrivateLinkServiceConnections: array? + + @description('Optional. Enable/Disable usage telemetry for module.') + enableTelemetry: bool? +}[]? diff --git a/modules/batch/batch-account/main.json b/modules/batch/batch-account/main.json index 3c256755bd..bb2a24b4de 100644 --- a/modules/batch/batch-account/main.json +++ b/modules/batch/batch-account/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.22.6.54827", - "templateHash": "2439163015108038599" + "templateHash": "1328678841391905998" }, "name": "Batch Accounts", "description": "This module deploys a Batch Account.", @@ -37,6 +37,221 @@ } }, "nullable": true + }, + "roleAssignmentType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "roleDefinitionIdOrName": { + "type": "string", + "metadata": { + "description": "Required. The name of the role to assign. If it cannot be found you can specify the role definition ID instead." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to." + } + }, + "principalType": { + "type": "string", + "allowedValues": [ + "Device", + "ForeignGroup", + "Group", + "ServicePrincipal", + "User" + ], + "nullable": true, + "metadata": { + "description": "Optional. The principal type of the assigned principal ID." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the role assignment." + } + }, + "condition": { + "type": "string", + "nullable": true, + "metadata": { + "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\"" + } + }, + "conditionVersion": { + "type": "string", + "allowedValues": [ + "2.0" + ], + "nullable": true, + "metadata": { + "description": "Optional. Version of the condition." + } + }, + "delegatedManagedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Resource Id of the delegated managed identity resource." + } + } + } + }, + "nullable": true + }, + "privateEndpointType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private endpoint." + } + }, + "location": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The location to deploy the private endpoint to." + } + }, + "service": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The service (sub-) type to deploy the private endpoint for. For example \"vault\" or \"blob\"." + } + }, + "subnetResourceId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the subnet where the endpoint needs to be created." + } + }, + "privateDnsZoneGroupName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided." + } + }, + "privateDnsZoneResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones." + } + }, + "customDnsConfigs": { + "type": "array", + "items": { + "type": "object", + "properties": { + "fqdn": { + "type": "string", + "nullable": true + }, + "ipAddresses": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. Custom DNS configurations." + } + }, + "ipConfigurations": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "groupId": { + "type": "string" + }, + "memberName": { + "type": "string" + }, + "privateIpAddress": { + "type": "string" + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints." + } + }, + "applicationSecurityGroupResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. Application security groups in which the private endpoint IP configuration is included." + } + }, + "customNetworkInterfaceName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The custom name of the network interface attached to the private endpoint." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "metadata": { + "description": "Optional. Specify the type of lock." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "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'." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags to be applied on all resources/resource groups in this deployment." + } + }, + "manualPrivateLinkServiceConnections": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. Manual PrivateLink Service Connections." + } + }, + "enableTelemetry": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + } + } + }, + "nullable": true } }, "parameters": { @@ -110,8 +325,7 @@ } }, "privateEndpoints": { - "type": "array", - "defaultValue": [], + "$ref": "#/definitions/privateEndpointType", "metadata": { "description": "Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible." } @@ -187,6 +401,12 @@ "description": "Optional. Tags of the resource." } }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "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'." + } + }, "allowedAuthenticationModes": { "type": "array", "defaultValue": [], @@ -301,7 +521,14 @@ "nodeIdentityReference": "[variables('nodeIdentityReference')]", "storageAccountId": "[parameters('storageAccountId')]" }, - "enableReferencedModulesTelemetry": false + "enableReferencedModulesTelemetry": false, + "builtInRoleNames": { + "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", + "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]", + "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]", + "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')]" + } }, "resources": { "cMKKeyVault::cMKKey": { @@ -391,14 +618,36 @@ "batchAccount" ] }, + "batchAccount_roleAssignments": { + "copy": { + "name": "batchAccount_roleAssignments", + "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Batch/batchAccounts/{0}', parameters('name'))]", + "name": "[guid(resourceId('Microsoft.Batch/batchAccounts', parameters('name')), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId, coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)]", + "properties": { + "roleDefinitionId": "[if(contains(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName), variables('builtInRoleNames')[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName], coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)]", + "principalId": "[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId]", + "description": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'description')]", + "principalType": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'principalType')]", + "condition": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition')]", + "conditionVersion": "[if(not(empty(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]", + "delegatedManagedIdentityResourceId": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]" + }, + "dependsOn": [ + "batchAccount" + ] + }, "batchAccount_privateEndpoints": { "copy": { "name": "batchAccount_privateEndpoints", - "count": "[length(parameters('privateEndpoints'))]" + "count": "[length(coalesce(parameters('privateEndpoints'), createArray()))]" }, "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", - "name": "[format('{0}-BatchAccount-PrivateEndpoint-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "name": "[format('{0}-batchAccount-PrivateEndpoint-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", "properties": { "expressionEvaluationOptions": { "scope": "inner" @@ -407,32 +656,54 @@ "parameters": { "groupIds": { "value": [ - "[parameters('privateEndpoints')[copyIndex()].service]" + "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'batchAccount')]" ] }, - "name": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'name'), createObject('value', parameters('privateEndpoints')[copyIndex()].name), createObject('value', format('pe-{0}-{1}-{2}', last(split(resourceId('Microsoft.Batch/batchAccounts', parameters('name')), '/')), parameters('privateEndpoints')[copyIndex()].service, copyIndex())))]", + "name": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'name'), format('pep-{0}-{1}-{2}', last(split(resourceId('Microsoft.Batch/batchAccounts', parameters('name')), '/')), coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'batchAccount'), copyIndex()))]" + }, "serviceResourceId": { "value": "[resourceId('Microsoft.Batch/batchAccounts', parameters('name'))]" }, "subnetResourceId": { - "value": "[parameters('privateEndpoints')[copyIndex()].subnetResourceId]" + "value": "[coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId]" }, "enableDefaultTelemetry": { - "value": "[variables('enableReferencedModulesTelemetry')]" + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'enableDefaultTelemetry'), variables('enableReferencedModulesTelemetry'))]" + }, + "location": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'location'), reference(split(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location)]" }, - "location": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'location'), createObject('value', parameters('privateEndpoints')[copyIndex()].location), createObject('value', reference(split(parameters('privateEndpoints')[copyIndex()].subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location))]", "lock": { - "value": "[coalesce(tryGet(parameters('privateEndpoints')[copyIndex()], 'lock'), parameters('lock'))]" - }, - "privateDnsZoneGroupName": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'privateDnsZoneGroupName'), createObject('value', parameters('privateEndpoints')[copyIndex()].privateDnsZoneGroupName), createObject('value', 'default'))]", - "privateDnsZoneResourceIds": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'privateDnsZoneResourceIds'), createObject('value', parameters('privateEndpoints')[copyIndex()].privateDnsZoneResourceIds), createObject('value', createArray()))]", - "roleAssignments": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'roleAssignments'), createObject('value', parameters('privateEndpoints')[copyIndex()].roleAssignments), createObject('value', createArray()))]", - "tags": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'tags'), createObject('value', parameters('privateEndpoints')[copyIndex()].tags), createObject('value', createObject()))]", - "manualPrivateLinkServiceConnections": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'manualPrivateLinkServiceConnections'), createObject('value', parameters('privateEndpoints')[copyIndex()].manualPrivateLinkServiceConnections), createObject('value', createArray()))]", - "customDnsConfigs": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'customDnsConfigs'), createObject('value', parameters('privateEndpoints')[copyIndex()].customDnsConfigs), createObject('value', createArray()))]", - "ipConfigurations": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'ipConfigurations'), createObject('value', parameters('privateEndpoints')[copyIndex()].ipConfigurations), createObject('value', createArray()))]", - "applicationSecurityGroupResourceIds": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'applicationSecurityGroupResourceIds'), createObject('value', parameters('privateEndpoints')[copyIndex()].applicationSecurityGroupResourceIds), createObject('value', createArray()))]", - "customNetworkInterfaceName": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'customNetworkInterfaceName'), createObject('value', parameters('privateEndpoints')[copyIndex()].customNetworkInterfaceName), createObject('value', ''))]" + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'lock'), parameters('lock'))]" + }, + "privateDnsZoneGroupName": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneGroupName')]" + }, + "privateDnsZoneResourceIds": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneResourceIds')]" + }, + "roleAssignments": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'roleAssignments')]" + }, + "tags": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'tags'), parameters('tags'))]" + }, + "manualPrivateLinkServiceConnections": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'manualPrivateLinkServiceConnections')]" + }, + "customDnsConfigs": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customDnsConfigs')]" + }, + "ipConfigurations": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'ipConfigurations')]" + }, + "applicationSecurityGroupResourceIds": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'applicationSecurityGroupResourceIds')]" + }, + "customNetworkInterfaceName": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customNetworkInterfaceName')]" + } }, "template": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", diff --git a/modules/cache/redis-enterprise/.test/common/main.test.bicep b/modules/cache/redis-enterprise/.test/common/main.test.bicep index dafcb37396..62c880c6f8 100644 --- a/modules/cache/redis-enterprise/.test/common/main.test.bicep +++ b/modules/cache/redis-enterprise/.test/common/main.test.bicep @@ -89,11 +89,8 @@ module testDeployment '../../main.bicep' = { privateEndpoints: [ { privateDnsZoneResourceIds: [ - nestedDependencies.outputs.privateDNSZoneResourceId - ] - service: 'redisEnterprise' subnetResourceId: nestedDependencies.outputs.subnetResourceId tags: { 'hidden-title': 'This is visible in the resource name' diff --git a/modules/cache/redis-enterprise/README.md b/modules/cache/redis-enterprise/README.md index 6b9779d29b..c8f5d1c37f 100644 --- a/modules/cache/redis-enterprise/README.md +++ b/modules/cache/redis-enterprise/README.md @@ -86,7 +86,6 @@ module redisEnterprise 'br:bicep/modules/cache.redis-enterprise:1.0.0' = { privateDnsZoneResourceIds: [ '' ] - service: 'redisEnterprise' subnetResourceId: '' tags: { Environment: 'Non-Prod' @@ -185,7 +184,6 @@ module redisEnterprise 'br:bicep/modules/cache.redis-enterprise:1.0.0' = { "privateDnsZoneResourceIds": [ "" ], - "service": "redisEnterprise", "subnetResourceId": "", "tags": { "Environment": "Non-Prod", @@ -546,7 +544,168 @@ The name of the Redis Cache Enterprise resource. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible. - Required: No - Type: array -- Default: `[]` + + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`applicationSecurityGroupResourceIds`](#parameter-privateendpointsapplicationsecuritygroupresourceids) | No | array | Optional. Application security groups in which the private endpoint IP configuration is included. | +| [`customDnsConfigs`](#parameter-privateendpointscustomdnsconfigs) | No | array | Optional. Custom DNS configurations. | +| [`customNetworkInterfaceName`](#parameter-privateendpointscustomnetworkinterfacename) | No | string | Optional. The custom name of the network interface attached to the private endpoint. | +| [`enableTelemetry`](#parameter-privateendpointsenabletelemetry) | No | bool | Optional. Enable/Disable usage telemetry for module. | +| [`ipConfigurations`](#parameter-privateendpointsipconfigurations) | No | array | Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints. | +| [`location`](#parameter-privateendpointslocation) | No | string | Optional. The location to deploy the private endpoint to. | +| [`lock`](#parameter-privateendpointslock) | No | object | Optional. Specify the type of lock. | +| [`manualPrivateLinkServiceConnections`](#parameter-privateendpointsmanualprivatelinkserviceconnections) | No | array | Optional. Manual PrivateLink Service Connections. | +| [`name`](#parameter-privateendpointsname) | No | string | Optional. The name of the private endpoint. | +| [`privateDnsZoneGroupName`](#parameter-privateendpointsprivatednszonegroupname) | No | string | Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided. | +| [`privateDnsZoneResourceIds`](#parameter-privateendpointsprivatednszoneresourceids) | No | array | Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones. | +| [`roleAssignments`](#parameter-privateendpointsroleassignments) | No | array | 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'. | +| [`service`](#parameter-privateendpointsservice) | No | string | Optional. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob". | +| [`subnetResourceId`](#parameter-privateendpointssubnetresourceid) | Yes | string | Required. Resource ID of the subnet where the endpoint needs to be created. | +| [`tags`](#parameter-privateendpointstags) | No | object | Optional. Tags to be applied on all resources/resource groups in this deployment. | + +### Parameter: `privateEndpoints.applicationSecurityGroupResourceIds` + +Optional. Application security groups in which the private endpoint IP configuration is included. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.customDnsConfigs` + +Optional. Custom DNS configurations. + +- Required: No +- Type: array + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`fqdn`](#parameter-privateendpointscustomdnsconfigsfqdn) | No | string | | +| [`ipAddresses`](#parameter-privateendpointscustomdnsconfigsipaddresses) | Yes | array | | + +### Parameter: `privateEndpoints.customDnsConfigs.fqdn` +- Required: No +- Type: string + +### Parameter: `privateEndpoints.customDnsConfigs.ipAddresses` +- Required: Yes +- Type: array + + +### Parameter: `privateEndpoints.customNetworkInterfaceName` + +Optional. The custom name of the network interface attached to the private endpoint. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.enableTelemetry` + +Optional. Enable/Disable usage telemetry for module. + +- Required: No +- Type: bool + +### Parameter: `privateEndpoints.ipConfigurations` + +Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints. + +- Required: No +- Type: array + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`groupId`](#parameter-privateendpointsipconfigurationsgroupid) | Yes | string | | +| [`memberName`](#parameter-privateendpointsipconfigurationsmembername) | Yes | string | | +| [`name`](#parameter-privateendpointsipconfigurationsname) | Yes | string | | +| [`privateIpAddress`](#parameter-privateendpointsipconfigurationsprivateipaddress) | Yes | string | | + +### Parameter: `privateEndpoints.ipConfigurations.groupId` +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.ipConfigurations.memberName` +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.ipConfigurations.name` +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.ipConfigurations.privateIpAddress` +- Required: Yes +- Type: string + + +### Parameter: `privateEndpoints.location` + +Optional. The location to deploy the private endpoint to. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.lock` + +Optional. Specify the type of lock. + +- Required: No +- Type: object + +### Parameter: `privateEndpoints.manualPrivateLinkServiceConnections` + +Optional. Manual PrivateLink Service Connections. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.name` + +Optional. The name of the private endpoint. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.privateDnsZoneGroupName` + +Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.privateDnsZoneResourceIds` + +Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.roleAssignments` + +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'. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.service` + +Optional. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob". + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.subnetResourceId` + +Required. Resource ID of the subnet where the endpoint needs to be created. + +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.tags` + +Optional. Tags to be applied on all resources/resource groups in this deployment. + +- Required: No +- Type: object ### Parameter: `roleAssignments` diff --git a/modules/cache/redis-enterprise/main.bicep b/modules/cache/redis-enterprise/main.bicep index 5def57823e..a8c918829d 100644 --- a/modules/cache/redis-enterprise/main.bicep +++ b/modules/cache/redis-enterprise/main.bicep @@ -44,7 +44,7 @@ param skuName string = 'Enterprise_E10' param zoneRedundant bool = true @description('Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible.') -param privateEndpoints array = [] +param privateEndpoints privateEndpointType @description('Optional. The databases to create in the Redis Cache Enterprise Cluster.') param databases array = [] @@ -198,27 +198,27 @@ module redisEnterprise_databases 'database/main.bicep' = [for (database, index) } }] -module redisEnterprise_privateEndpoints '../../network/private-endpoint/main.bicep' = [for (privateEndpoint, index) in privateEndpoints: { - name: '${uniqueString(deployment().name, location)}-redisCacheEnterprise-PrivateEndpoint-${index}' +module redisEnterprise_privateEndpoints '../../network/private-endpoint/main.bicep' = [for (privateEndpoint, index) in (privateEndpoints ?? []): { + name: '${uniqueString(deployment().name, location)}-redisEnterprise-PrivateEndpoint-${index}' params: { groupIds: [ - privateEndpoint.service + privateEndpoint.?service ?? 'redisEnterprise' ] - name: contains(privateEndpoint, 'name') ? privateEndpoint.name : 'pe-${last(split(redisEnterprise.id, '/'))}-${privateEndpoint.service}-${index}' + name: privateEndpoint.?name ?? 'pep-${last(split(redisEnterprise.id, '/'))}-${privateEndpoint.?service ?? 'redisEnterprise'}-${index}' serviceResourceId: redisEnterprise.id subnetResourceId: privateEndpoint.subnetResourceId - enableDefaultTelemetry: enableReferencedModulesTelemetry - location: contains(privateEndpoint, 'location') ? privateEndpoint.location : reference(split(privateEndpoint.subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location + enableDefaultTelemetry: privateEndpoint.?enableDefaultTelemetry ?? enableReferencedModulesTelemetry + location: privateEndpoint.?location ?? reference(split(privateEndpoint.subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location lock: privateEndpoint.?lock ?? lock - privateDnsZoneGroupName: contains(privateEndpoint, 'privateDnsZoneGroupName') ? privateEndpoint.privateDnsZoneGroupName : 'default' - privateDnsZoneResourceIds: contains(privateEndpoint, 'privateDnsZoneResourceIds') ? privateEndpoint.privateDnsZoneResourceIds : [] - 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 : [] - applicationSecurityGroupResourceIds: contains(privateEndpoint, 'applicationSecurityGroupResourceIds') ? privateEndpoint.applicationSecurityGroupResourceIds : [] - customNetworkInterfaceName: contains(privateEndpoint, 'customNetworkInterfaceName') ? privateEndpoint.customNetworkInterfaceName : '' + privateDnsZoneGroupName: privateEndpoint.?privateDnsZoneGroupName + privateDnsZoneResourceIds: privateEndpoint.?privateDnsZoneResourceIds + roleAssignments: privateEndpoint.?roleAssignments + tags: privateEndpoint.?tags ?? tags + manualPrivateLinkServiceConnections: privateEndpoint.?manualPrivateLinkServiceConnections + customDnsConfigs: privateEndpoint.?customDnsConfigs + ipConfigurations: privateEndpoint.?ipConfigurations + applicationSecurityGroupResourceIds: privateEndpoint.?applicationSecurityGroupResourceIds + customNetworkInterfaceName: privateEndpoint.?customNetworkInterfaceName } }] @@ -271,3 +271,58 @@ type roleAssignmentType = { @description('Optional. The Resource Id of the delegated managed identity resource.') delegatedManagedIdentityResourceId: string? }[]? + +type privateEndpointType = { + @description('Optional. The name of the private endpoint.') + name: string? + + @description('Optional. The location to deploy the private endpoint to.') + location: string? + + @description('Optional. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob".') + service: string? + + @description('Required. Resource ID of the subnet where the endpoint needs to be created.') + subnetResourceId: string + + @description('Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided.') + privateDnsZoneGroupName: string? + + @description('Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones.') + privateDnsZoneResourceIds: string[]? + + @description('Optional. Custom DNS configurations.') + customDnsConfigs: { + fqdn: string? + ipAddresses: string[] + }[]? + + @description('Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints.') + ipConfigurations: { + name: string + groupId: string + memberName: string + privateIpAddress: string + }[]? + + @description('Optional. Application security groups in which the private endpoint IP configuration is included.') + applicationSecurityGroupResourceIds: string[]? + + @description('Optional. The custom name of the network interface attached to the private endpoint.') + customNetworkInterfaceName: string? + + @description('Optional. Specify the type of lock.') + lock: lockType + + @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\'.') + roleAssignments: roleAssignmentType + + @description('Optional. Tags to be applied on all resources/resource groups in this deployment.') + tags: object? + + @description('Optional. Manual PrivateLink Service Connections.') + manualPrivateLinkServiceConnections: array? + + @description('Optional. Enable/Disable usage telemetry for module.') + enableTelemetry: bool? +}[]? diff --git a/modules/cache/redis-enterprise/main.json b/modules/cache/redis-enterprise/main.json index dd581fe4b8..31d6df1989 100644 --- a/modules/cache/redis-enterprise/main.json +++ b/modules/cache/redis-enterprise/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.22.6.54827", - "templateHash": "6097715803536632685" + "templateHash": "12857398091231906452" }, "name": "Redis Cache Enterprise", "description": "This module deploys a Redis Cache Enterprise.", @@ -103,6 +103,155 @@ } }, "nullable": true + }, + "privateEndpointType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private endpoint." + } + }, + "location": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The location to deploy the private endpoint to." + } + }, + "service": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The service (sub-) type to deploy the private endpoint for. For example \"vault\" or \"blob\"." + } + }, + "subnetResourceId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the subnet where the endpoint needs to be created." + } + }, + "privateDnsZoneGroupName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided." + } + }, + "privateDnsZoneResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones." + } + }, + "customDnsConfigs": { + "type": "array", + "items": { + "type": "object", + "properties": { + "fqdn": { + "type": "string", + "nullable": true + }, + "ipAddresses": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. Custom DNS configurations." + } + }, + "ipConfigurations": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "groupId": { + "type": "string" + }, + "memberName": { + "type": "string" + }, + "privateIpAddress": { + "type": "string" + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints." + } + }, + "applicationSecurityGroupResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. Application security groups in which the private endpoint IP configuration is included." + } + }, + "customNetworkInterfaceName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The custom name of the network interface attached to the private endpoint." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "metadata": { + "description": "Optional. Specify the type of lock." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "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'." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags to be applied on all resources/resource groups in this deployment." + } + }, + "manualPrivateLinkServiceConnections": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. Manual PrivateLink Service Connections." + } + }, + "enableTelemetry": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + } + } + }, + "nullable": true } }, "parameters": { @@ -181,8 +330,7 @@ } }, "privateEndpoints": { - "type": "array", - "defaultValue": [], + "$ref": "#/definitions/privateEndpointType", "metadata": { "description": "Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible." } @@ -614,11 +762,11 @@ "redisEnterprise_privateEndpoints": { "copy": { "name": "redisEnterprise_privateEndpoints", - "count": "[length(parameters('privateEndpoints'))]" + "count": "[length(coalesce(parameters('privateEndpoints'), createArray()))]" }, "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", - "name": "[format('{0}-redisCacheEnterprise-PrivateEndpoint-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "name": "[format('{0}-redisEnterprise-PrivateEndpoint-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", "properties": { "expressionEvaluationOptions": { "scope": "inner" @@ -627,32 +775,54 @@ "parameters": { "groupIds": { "value": [ - "[parameters('privateEndpoints')[copyIndex()].service]" + "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'redisEnterprise')]" ] }, - "name": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'name'), createObject('value', parameters('privateEndpoints')[copyIndex()].name), createObject('value', format('pe-{0}-{1}-{2}', last(split(resourceId('Microsoft.Cache/redisEnterprise', parameters('name')), '/')), parameters('privateEndpoints')[copyIndex()].service, copyIndex())))]", + "name": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'name'), format('pep-{0}-{1}-{2}', last(split(resourceId('Microsoft.Cache/redisEnterprise', parameters('name')), '/')), coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'redisEnterprise'), copyIndex()))]" + }, "serviceResourceId": { "value": "[resourceId('Microsoft.Cache/redisEnterprise', parameters('name'))]" }, "subnetResourceId": { - "value": "[parameters('privateEndpoints')[copyIndex()].subnetResourceId]" + "value": "[coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId]" }, "enableDefaultTelemetry": { - "value": "[variables('enableReferencedModulesTelemetry')]" + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'enableDefaultTelemetry'), variables('enableReferencedModulesTelemetry'))]" + }, + "location": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'location'), reference(split(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location)]" }, - "location": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'location'), createObject('value', parameters('privateEndpoints')[copyIndex()].location), createObject('value', reference(split(parameters('privateEndpoints')[copyIndex()].subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location))]", "lock": { - "value": "[coalesce(tryGet(parameters('privateEndpoints')[copyIndex()], 'lock'), parameters('lock'))]" + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'lock'), parameters('lock'))]" + }, + "privateDnsZoneGroupName": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneGroupName')]" + }, + "privateDnsZoneResourceIds": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneResourceIds')]" + }, + "roleAssignments": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'roleAssignments')]" }, - "privateDnsZoneGroupName": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'privateDnsZoneGroupName'), createObject('value', parameters('privateEndpoints')[copyIndex()].privateDnsZoneGroupName), createObject('value', 'default'))]", - "privateDnsZoneResourceIds": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'privateDnsZoneResourceIds'), createObject('value', parameters('privateEndpoints')[copyIndex()].privateDnsZoneResourceIds), createObject('value', createArray()))]", - "roleAssignments": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'roleAssignments'), createObject('value', parameters('privateEndpoints')[copyIndex()].roleAssignments), createObject('value', createArray()))]", - "tags": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'tags'), createObject('value', parameters('privateEndpoints')[copyIndex()].tags), createObject('value', createObject()))]", - "manualPrivateLinkServiceConnections": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'manualPrivateLinkServiceConnections'), createObject('value', parameters('privateEndpoints')[copyIndex()].manualPrivateLinkServiceConnections), createObject('value', createArray()))]", - "customDnsConfigs": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'customDnsConfigs'), createObject('value', parameters('privateEndpoints')[copyIndex()].customDnsConfigs), createObject('value', createArray()))]", - "ipConfigurations": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'ipConfigurations'), createObject('value', parameters('privateEndpoints')[copyIndex()].ipConfigurations), createObject('value', createArray()))]", - "applicationSecurityGroupResourceIds": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'applicationSecurityGroupResourceIds'), createObject('value', parameters('privateEndpoints')[copyIndex()].applicationSecurityGroupResourceIds), createObject('value', createArray()))]", - "customNetworkInterfaceName": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'customNetworkInterfaceName'), createObject('value', parameters('privateEndpoints')[copyIndex()].customNetworkInterfaceName), createObject('value', ''))]" + "tags": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'tags'), parameters('tags'))]" + }, + "manualPrivateLinkServiceConnections": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'manualPrivateLinkServiceConnections')]" + }, + "customDnsConfigs": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customDnsConfigs')]" + }, + "ipConfigurations": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'ipConfigurations')]" + }, + "applicationSecurityGroupResourceIds": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'applicationSecurityGroupResourceIds')]" + }, + "customNetworkInterfaceName": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customNetworkInterfaceName')]" + } }, "template": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", diff --git a/modules/cache/redis/.test/common/main.test.bicep b/modules/cache/redis/.test/common/main.test.bicep index 2b0142168f..9e37b1ba2c 100644 --- a/modules/cache/redis/.test/common/main.test.bicep +++ b/modules/cache/redis/.test/common/main.test.bicep @@ -83,11 +83,8 @@ module testDeployment '../../main.bicep' = { privateEndpoints: [ { privateDnsZoneResourceIds: [ - nestedDependencies.outputs.privateDNSZoneResourceId - ] - service: 'redisCache' subnetResourceId: nestedDependencies.outputs.subnetResourceId tags: { 'hidden-title': 'This is visible in the resource name' diff --git a/modules/cache/redis/README.md b/modules/cache/redis/README.md index 1b199e2075..3a725e5ead 100644 --- a/modules/cache/redis/README.md +++ b/modules/cache/redis/README.md @@ -67,7 +67,6 @@ module redis 'br:bicep/modules/cache.redis:1.0.0' = { privateDnsZoneResourceIds: [ '' ] - service: 'redisCache' subnetResourceId: '' tags: { Environment: 'Non-Prod' @@ -150,7 +149,6 @@ module redis 'br:bicep/modules/cache.redis:1.0.0' = { "privateDnsZoneResourceIds": [ "" ], - "service": "redisCache", "subnetResourceId": "", "tags": { "Environment": "Non-Prod", @@ -415,7 +413,168 @@ The name of the Redis cache resource. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible. - Required: No - Type: array -- Default: `[]` + + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`applicationSecurityGroupResourceIds`](#parameter-privateendpointsapplicationsecuritygroupresourceids) | No | array | Optional. Application security groups in which the private endpoint IP configuration is included. | +| [`customDnsConfigs`](#parameter-privateendpointscustomdnsconfigs) | No | array | Optional. Custom DNS configurations. | +| [`customNetworkInterfaceName`](#parameter-privateendpointscustomnetworkinterfacename) | No | string | Optional. The custom name of the network interface attached to the private endpoint. | +| [`enableTelemetry`](#parameter-privateendpointsenabletelemetry) | No | bool | Optional. Enable/Disable usage telemetry for module. | +| [`ipConfigurations`](#parameter-privateendpointsipconfigurations) | No | array | Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints. | +| [`location`](#parameter-privateendpointslocation) | No | string | Optional. The location to deploy the private endpoint to. | +| [`lock`](#parameter-privateendpointslock) | No | object | Optional. Specify the type of lock. | +| [`manualPrivateLinkServiceConnections`](#parameter-privateendpointsmanualprivatelinkserviceconnections) | No | array | Optional. Manual PrivateLink Service Connections. | +| [`name`](#parameter-privateendpointsname) | No | string | Optional. The name of the private endpoint. | +| [`privateDnsZoneGroupName`](#parameter-privateendpointsprivatednszonegroupname) | No | string | Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided. | +| [`privateDnsZoneResourceIds`](#parameter-privateendpointsprivatednszoneresourceids) | No | array | Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones. | +| [`roleAssignments`](#parameter-privateendpointsroleassignments) | No | array | 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'. | +| [`service`](#parameter-privateendpointsservice) | No | string | Optional. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob". | +| [`subnetResourceId`](#parameter-privateendpointssubnetresourceid) | Yes | string | Required. Resource ID of the subnet where the endpoint needs to be created. | +| [`tags`](#parameter-privateendpointstags) | No | object | Optional. Tags to be applied on all resources/resource groups in this deployment. | + +### Parameter: `privateEndpoints.applicationSecurityGroupResourceIds` + +Optional. Application security groups in which the private endpoint IP configuration is included. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.customDnsConfigs` + +Optional. Custom DNS configurations. + +- Required: No +- Type: array + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`fqdn`](#parameter-privateendpointscustomdnsconfigsfqdn) | No | string | | +| [`ipAddresses`](#parameter-privateendpointscustomdnsconfigsipaddresses) | Yes | array | | + +### Parameter: `privateEndpoints.customDnsConfigs.fqdn` +- Required: No +- Type: string + +### Parameter: `privateEndpoints.customDnsConfigs.ipAddresses` +- Required: Yes +- Type: array + + +### Parameter: `privateEndpoints.customNetworkInterfaceName` + +Optional. The custom name of the network interface attached to the private endpoint. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.enableTelemetry` + +Optional. Enable/Disable usage telemetry for module. + +- Required: No +- Type: bool + +### Parameter: `privateEndpoints.ipConfigurations` + +Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints. + +- Required: No +- Type: array + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`groupId`](#parameter-privateendpointsipconfigurationsgroupid) | Yes | string | | +| [`memberName`](#parameter-privateendpointsipconfigurationsmembername) | Yes | string | | +| [`name`](#parameter-privateendpointsipconfigurationsname) | Yes | string | | +| [`privateIpAddress`](#parameter-privateendpointsipconfigurationsprivateipaddress) | Yes | string | | + +### Parameter: `privateEndpoints.ipConfigurations.groupId` +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.ipConfigurations.memberName` +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.ipConfigurations.name` +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.ipConfigurations.privateIpAddress` +- Required: Yes +- Type: string + + +### Parameter: `privateEndpoints.location` + +Optional. The location to deploy the private endpoint to. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.lock` + +Optional. Specify the type of lock. + +- Required: No +- Type: object + +### Parameter: `privateEndpoints.manualPrivateLinkServiceConnections` + +Optional. Manual PrivateLink Service Connections. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.name` + +Optional. The name of the private endpoint. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.privateDnsZoneGroupName` + +Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.privateDnsZoneResourceIds` + +Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.roleAssignments` + +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'. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.service` + +Optional. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob". + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.subnetResourceId` + +Required. Resource ID of the subnet where the endpoint needs to be created. + +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.tags` + +Optional. Tags to be applied on all resources/resource groups in this deployment. + +- Required: No +- Type: object ### Parameter: `publicNetworkAccess` diff --git a/modules/cache/redis/main.bicep b/modules/cache/redis/main.bicep index eadf8ece68..947bf9d72c 100644 --- a/modules/cache/redis/main.bicep +++ b/modules/cache/redis/main.bicep @@ -100,7 +100,7 @@ param zoneRedundant bool = true param zones array = [] @description('Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible.') -param privateEndpoints array = [] +param privateEndpoints privateEndpointType @description('Optional. The name of the diagnostic setting, if deployed. If left empty, it defaults to "-diagnosticSettings".') param diagnosticSettingsName string = '' @@ -250,27 +250,27 @@ resource redis_roleAssignments 'Microsoft.Authorization/roleAssignments@2022-04- scope: redis }] -module redis_privateEndpoints '../../network/private-endpoint/main.bicep' = [for (privateEndpoint, index) in privateEndpoints: { - name: '${uniqueString(deployment().name, location)}-redisCache-PrivateEndpoint-${index}' +module redis_privateEndpoints '../../network/private-endpoint/main.bicep' = [for (privateEndpoint, index) in (privateEndpoints ?? []): { + name: '${uniqueString(deployment().name, location)}-redis-PrivateEndpoint-${index}' params: { groupIds: [ - privateEndpoint.service + privateEndpoint.?service ?? 'redisCache' ] - name: contains(privateEndpoint, 'name') ? privateEndpoint.name : 'pe-${last(split(redis.id, '/'))}-${privateEndpoint.service}-${index}' + name: privateEndpoint.?name ?? 'pep-${last(split(redis.id, '/'))}-${privateEndpoint.?service ?? 'redisCache'}-${index}' serviceResourceId: redis.id subnetResourceId: privateEndpoint.subnetResourceId - enableDefaultTelemetry: enableReferencedModulesTelemetry - location: contains(privateEndpoint, 'location') ? privateEndpoint.location : reference(split(privateEndpoint.subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location + enableDefaultTelemetry: privateEndpoint.?enableDefaultTelemetry ?? enableReferencedModulesTelemetry + location: privateEndpoint.?location ?? reference(split(privateEndpoint.subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location lock: privateEndpoint.?lock ?? lock - privateDnsZoneGroupName: contains(privateEndpoint, 'privateDnsZoneGroupName') ? privateEndpoint.privateDnsZoneGroupName : 'default' - privateDnsZoneResourceIds: contains(privateEndpoint, 'privateDnsZoneResourceIds') ? privateEndpoint.privateDnsZoneResourceIds : [] - 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 : [] - applicationSecurityGroupResourceIds: contains(privateEndpoint, 'applicationSecurityGroupResourceIds') ? privateEndpoint.applicationSecurityGroupResourceIds : [] - customNetworkInterfaceName: contains(privateEndpoint, 'customNetworkInterfaceName') ? privateEndpoint.customNetworkInterfaceName : '' + privateDnsZoneGroupName: privateEndpoint.?privateDnsZoneGroupName + privateDnsZoneResourceIds: privateEndpoint.?privateDnsZoneResourceIds + roleAssignments: privateEndpoint.?roleAssignments + tags: privateEndpoint.?tags ?? tags + manualPrivateLinkServiceConnections: privateEndpoint.?manualPrivateLinkServiceConnections + customDnsConfigs: privateEndpoint.?customDnsConfigs + ipConfigurations: privateEndpoint.?ipConfigurations + applicationSecurityGroupResourceIds: privateEndpoint.?applicationSecurityGroupResourceIds + customNetworkInterfaceName: privateEndpoint.?customNetworkInterfaceName } }] @@ -329,3 +329,58 @@ type roleAssignmentType = { @description('Optional. The Resource Id of the delegated managed identity resource.') delegatedManagedIdentityResourceId: string? }[]? + +type privateEndpointType = { + @description('Optional. The name of the private endpoint.') + name: string? + + @description('Optional. The location to deploy the private endpoint to.') + location: string? + + @description('Optional. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob".') + service: string? + + @description('Required. Resource ID of the subnet where the endpoint needs to be created.') + subnetResourceId: string + + @description('Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided.') + privateDnsZoneGroupName: string? + + @description('Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones.') + privateDnsZoneResourceIds: string[]? + + @description('Optional. Custom DNS configurations.') + customDnsConfigs: { + fqdn: string? + ipAddresses: string[] + }[]? + + @description('Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints.') + ipConfigurations: { + name: string + groupId: string + memberName: string + privateIpAddress: string + }[]? + + @description('Optional. Application security groups in which the private endpoint IP configuration is included.') + applicationSecurityGroupResourceIds: string[]? + + @description('Optional. The custom name of the network interface attached to the private endpoint.') + customNetworkInterfaceName: string? + + @description('Optional. Specify the type of lock.') + lock: lockType + + @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\'.') + roleAssignments: roleAssignmentType + + @description('Optional. Tags to be applied on all resources/resource groups in this deployment.') + tags: object? + + @description('Optional. Manual PrivateLink Service Connections.') + manualPrivateLinkServiceConnections: array? + + @description('Optional. Enable/Disable usage telemetry for module.') + enableTelemetry: bool? +}[]? diff --git a/modules/cache/redis/main.json b/modules/cache/redis/main.json index 97179d1921..063248bb2f 100644 --- a/modules/cache/redis/main.json +++ b/modules/cache/redis/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.22.6.54827", - "templateHash": "14560598039949913276" + "templateHash": "17149457763698369113" }, "name": "Redis Cache", "description": "This module deploys a Redis Cache.", @@ -103,6 +103,155 @@ } }, "nullable": true + }, + "privateEndpointType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private endpoint." + } + }, + "location": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The location to deploy the private endpoint to." + } + }, + "service": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The service (sub-) type to deploy the private endpoint for. For example \"vault\" or \"blob\"." + } + }, + "subnetResourceId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the subnet where the endpoint needs to be created." + } + }, + "privateDnsZoneGroupName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided." + } + }, + "privateDnsZoneResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones." + } + }, + "customDnsConfigs": { + "type": "array", + "items": { + "type": "object", + "properties": { + "fqdn": { + "type": "string", + "nullable": true + }, + "ipAddresses": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. Custom DNS configurations." + } + }, + "ipConfigurations": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "groupId": { + "type": "string" + }, + "memberName": { + "type": "string" + }, + "privateIpAddress": { + "type": "string" + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints." + } + }, + "applicationSecurityGroupResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. Application security groups in which the private endpoint IP configuration is included." + } + }, + "customNetworkInterfaceName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The custom name of the network interface attached to the private endpoint." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "metadata": { + "description": "Optional. Specify the type of lock." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "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'." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags to be applied on all resources/resource groups in this deployment." + } + }, + "manualPrivateLinkServiceConnections": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. Manual PrivateLink Service Connections." + } + }, + "enableTelemetry": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + } + } + }, + "nullable": true } }, "parameters": { @@ -289,8 +438,7 @@ } }, "privateEndpoints": { - "type": "array", - "defaultValue": [], + "$ref": "#/definitions/privateEndpointType", "metadata": { "description": "Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible." } @@ -500,11 +648,11 @@ "redis_privateEndpoints": { "copy": { "name": "redis_privateEndpoints", - "count": "[length(parameters('privateEndpoints'))]" + "count": "[length(coalesce(parameters('privateEndpoints'), createArray()))]" }, "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", - "name": "[format('{0}-redisCache-PrivateEndpoint-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "name": "[format('{0}-redis-PrivateEndpoint-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", "properties": { "expressionEvaluationOptions": { "scope": "inner" @@ -513,32 +661,54 @@ "parameters": { "groupIds": { "value": [ - "[parameters('privateEndpoints')[copyIndex()].service]" + "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'redisCache')]" ] }, - "name": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'name'), createObject('value', parameters('privateEndpoints')[copyIndex()].name), createObject('value', format('pe-{0}-{1}-{2}', last(split(resourceId('Microsoft.Cache/redis', parameters('name')), '/')), parameters('privateEndpoints')[copyIndex()].service, copyIndex())))]", + "name": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'name'), format('pep-{0}-{1}-{2}', last(split(resourceId('Microsoft.Cache/redis', parameters('name')), '/')), coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'redisCache'), copyIndex()))]" + }, "serviceResourceId": { "value": "[resourceId('Microsoft.Cache/redis', parameters('name'))]" }, "subnetResourceId": { - "value": "[parameters('privateEndpoints')[copyIndex()].subnetResourceId]" + "value": "[coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId]" }, "enableDefaultTelemetry": { - "value": "[variables('enableReferencedModulesTelemetry')]" + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'enableDefaultTelemetry'), variables('enableReferencedModulesTelemetry'))]" + }, + "location": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'location'), reference(split(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location)]" }, - "location": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'location'), createObject('value', parameters('privateEndpoints')[copyIndex()].location), createObject('value', reference(split(parameters('privateEndpoints')[copyIndex()].subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location))]", "lock": { - "value": "[coalesce(tryGet(parameters('privateEndpoints')[copyIndex()], 'lock'), parameters('lock'))]" + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'lock'), parameters('lock'))]" + }, + "privateDnsZoneGroupName": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneGroupName')]" + }, + "privateDnsZoneResourceIds": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneResourceIds')]" + }, + "roleAssignments": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'roleAssignments')]" }, - "privateDnsZoneGroupName": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'privateDnsZoneGroupName'), createObject('value', parameters('privateEndpoints')[copyIndex()].privateDnsZoneGroupName), createObject('value', 'default'))]", - "privateDnsZoneResourceIds": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'privateDnsZoneResourceIds'), createObject('value', parameters('privateEndpoints')[copyIndex()].privateDnsZoneResourceIds), createObject('value', createArray()))]", - "roleAssignments": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'roleAssignments'), createObject('value', parameters('privateEndpoints')[copyIndex()].roleAssignments), createObject('value', createArray()))]", - "tags": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'tags'), createObject('value', parameters('privateEndpoints')[copyIndex()].tags), createObject('value', createObject()))]", - "manualPrivateLinkServiceConnections": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'manualPrivateLinkServiceConnections'), createObject('value', parameters('privateEndpoints')[copyIndex()].manualPrivateLinkServiceConnections), createObject('value', createArray()))]", - "customDnsConfigs": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'customDnsConfigs'), createObject('value', parameters('privateEndpoints')[copyIndex()].customDnsConfigs), createObject('value', createArray()))]", - "ipConfigurations": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'ipConfigurations'), createObject('value', parameters('privateEndpoints')[copyIndex()].ipConfigurations), createObject('value', createArray()))]", - "applicationSecurityGroupResourceIds": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'applicationSecurityGroupResourceIds'), createObject('value', parameters('privateEndpoints')[copyIndex()].applicationSecurityGroupResourceIds), createObject('value', createArray()))]", - "customNetworkInterfaceName": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'customNetworkInterfaceName'), createObject('value', parameters('privateEndpoints')[copyIndex()].customNetworkInterfaceName), createObject('value', ''))]" + "tags": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'tags'), parameters('tags'))]" + }, + "manualPrivateLinkServiceConnections": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'manualPrivateLinkServiceConnections')]" + }, + "customDnsConfigs": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customDnsConfigs')]" + }, + "ipConfigurations": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'ipConfigurations')]" + }, + "applicationSecurityGroupResourceIds": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'applicationSecurityGroupResourceIds')]" + }, + "customNetworkInterfaceName": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customNetworkInterfaceName')]" + } }, "template": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", diff --git a/modules/cognitive-services/account/.test/common/main.test.bicep b/modules/cognitive-services/account/.test/common/main.test.bicep index 4c1d011d2e..45695d9d0f 100644 --- a/modules/cognitive-services/account/.test/common/main.test.bicep +++ b/modules/cognitive-services/account/.test/common/main.test.bicep @@ -108,7 +108,6 @@ module testDeployment '../../main.bicep' = { privateDnsZoneResourceIds: [ nestedDependencies.outputs.privateDNSZoneResourceId ] - service: 'account' subnetResourceId: nestedDependencies.outputs.subnetResourceId tags: { 'hidden-title': 'This is visible in the resource name' diff --git a/modules/cognitive-services/account/README.md b/modules/cognitive-services/account/README.md index f4a53494f4..f156a8eb2b 100644 --- a/modules/cognitive-services/account/README.md +++ b/modules/cognitive-services/account/README.md @@ -81,7 +81,6 @@ module account 'br:bicep/modules/cognitive-services.account:1.0.0' = { privateDnsZoneResourceIds: [ '' ] - service: 'account' subnetResourceId: '' tags: { Environment: 'Non-Prod' @@ -177,7 +176,6 @@ module account 'br:bicep/modules/cognitive-services.account:1.0.0' = { "privateDnsZoneResourceIds": [ "" ], - "service": "account", "subnetResourceId": "", "tags": { "Environment": "Non-Prod", @@ -703,7 +701,168 @@ A collection of rules governing the accessibility from specific network location Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible. - Required: No - Type: array -- Default: `[]` + + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`applicationSecurityGroupResourceIds`](#parameter-privateendpointsapplicationsecuritygroupresourceids) | No | array | Optional. Application security groups in which the private endpoint IP configuration is included. | +| [`customDnsConfigs`](#parameter-privateendpointscustomdnsconfigs) | No | array | Optional. Custom DNS configurations. | +| [`customNetworkInterfaceName`](#parameter-privateendpointscustomnetworkinterfacename) | No | string | Optional. The custom name of the network interface attached to the private endpoint. | +| [`enableTelemetry`](#parameter-privateendpointsenabletelemetry) | No | bool | Optional. Enable/Disable usage telemetry for module. | +| [`ipConfigurations`](#parameter-privateendpointsipconfigurations) | No | array | Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints. | +| [`location`](#parameter-privateendpointslocation) | No | string | Optional. The location to deploy the private endpoint to. | +| [`lock`](#parameter-privateendpointslock) | No | object | Optional. Specify the type of lock. | +| [`manualPrivateLinkServiceConnections`](#parameter-privateendpointsmanualprivatelinkserviceconnections) | No | array | Optional. Manual PrivateLink Service Connections. | +| [`name`](#parameter-privateendpointsname) | No | string | Optional. The name of the private endpoint. | +| [`privateDnsZoneGroupName`](#parameter-privateendpointsprivatednszonegroupname) | No | string | Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided. | +| [`privateDnsZoneResourceIds`](#parameter-privateendpointsprivatednszoneresourceids) | No | array | Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones. | +| [`roleAssignments`](#parameter-privateendpointsroleassignments) | No | array | 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'. | +| [`service`](#parameter-privateendpointsservice) | No | string | Optional. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob". | +| [`subnetResourceId`](#parameter-privateendpointssubnetresourceid) | Yes | string | Required. Resource ID of the subnet where the endpoint needs to be created. | +| [`tags`](#parameter-privateendpointstags) | No | object | Optional. Tags to be applied on all resources/resource groups in this deployment. | + +### Parameter: `privateEndpoints.applicationSecurityGroupResourceIds` + +Optional. Application security groups in which the private endpoint IP configuration is included. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.customDnsConfigs` + +Optional. Custom DNS configurations. + +- Required: No +- Type: array + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`fqdn`](#parameter-privateendpointscustomdnsconfigsfqdn) | No | string | | +| [`ipAddresses`](#parameter-privateendpointscustomdnsconfigsipaddresses) | Yes | array | | + +### Parameter: `privateEndpoints.customDnsConfigs.fqdn` +- Required: No +- Type: string + +### Parameter: `privateEndpoints.customDnsConfigs.ipAddresses` +- Required: Yes +- Type: array + + +### Parameter: `privateEndpoints.customNetworkInterfaceName` + +Optional. The custom name of the network interface attached to the private endpoint. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.enableTelemetry` + +Optional. Enable/Disable usage telemetry for module. + +- Required: No +- Type: bool + +### Parameter: `privateEndpoints.ipConfigurations` + +Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints. + +- Required: No +- Type: array + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`groupId`](#parameter-privateendpointsipconfigurationsgroupid) | Yes | string | | +| [`memberName`](#parameter-privateendpointsipconfigurationsmembername) | Yes | string | | +| [`name`](#parameter-privateendpointsipconfigurationsname) | Yes | string | | +| [`privateIpAddress`](#parameter-privateendpointsipconfigurationsprivateipaddress) | Yes | string | | + +### Parameter: `privateEndpoints.ipConfigurations.groupId` +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.ipConfigurations.memberName` +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.ipConfigurations.name` +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.ipConfigurations.privateIpAddress` +- Required: Yes +- Type: string + + +### Parameter: `privateEndpoints.location` + +Optional. The location to deploy the private endpoint to. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.lock` + +Optional. Specify the type of lock. + +- Required: No +- Type: object + +### Parameter: `privateEndpoints.manualPrivateLinkServiceConnections` + +Optional. Manual PrivateLink Service Connections. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.name` + +Optional. The name of the private endpoint. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.privateDnsZoneGroupName` + +Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.privateDnsZoneResourceIds` + +Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.roleAssignments` + +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'. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.service` + +Optional. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob". + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.subnetResourceId` + +Required. Resource ID of the subnet where the endpoint needs to be created. + +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.tags` + +Optional. Tags to be applied on all resources/resource groups in this deployment. + +- Required: No +- Type: object ### Parameter: `publicNetworkAccess` diff --git a/modules/cognitive-services/account/main.bicep b/modules/cognitive-services/account/main.bicep index 80194a13c1..0f980e98b5 100644 --- a/modules/cognitive-services/account/main.bicep +++ b/modules/cognitive-services/account/main.bicep @@ -84,7 +84,7 @@ param customSubDomainName string = '' param networkAcls object = {} @description('Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible.') -param privateEndpoints array = [] +param privateEndpoints privateEndpointType @description('Optional. Enables system assigned managed identity on the resource.') param systemAssignedIdentity bool = false @@ -306,27 +306,27 @@ resource cognitiveServices_diagnosticSettingName 'Microsoft.Insights/diagnostics scope: cognitiveServices } -module cognitiveServices_privateEndpoints '../../network/private-endpoint/main.bicep' = [for (privateEndpoint, index) in privateEndpoints: { - name: '${uniqueString(deployment().name, location)}-CognitiveServices-PrivateEndpoint-${index}' +module cognitiveServices_privateEndpoints '../../network/private-endpoint/main.bicep' = [for (privateEndpoint, index) in (privateEndpoints ?? []): { + name: '${uniqueString(deployment().name, location)}-cognitiveServices-PrivateEndpoint-${index}' params: { groupIds: [ - privateEndpoint.service + privateEndpoint.?service ?? 'account' ] - name: contains(privateEndpoint, 'name') ? privateEndpoint.name : 'pe-${last(split(cognitiveServices.id, '/'))}-${privateEndpoint.service}-${index}' + name: privateEndpoint.?name ?? 'pep-${last(split(cognitiveServices.id, '/'))}-${privateEndpoint.?service ?? 'account'}-${index}' serviceResourceId: cognitiveServices.id subnetResourceId: privateEndpoint.subnetResourceId - enableDefaultTelemetry: enableReferencedModulesTelemetry - location: contains(privateEndpoint, 'location') ? privateEndpoint.location : reference(split(privateEndpoint.subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location + enableDefaultTelemetry: privateEndpoint.?enableDefaultTelemetry ?? enableReferencedModulesTelemetry + location: privateEndpoint.?location ?? reference(split(privateEndpoint.subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location lock: privateEndpoint.?lock ?? lock - privateDnsZoneGroupName: contains(privateEndpoint, 'privateDnsZoneGroupName') ? privateEndpoint.privateDnsZoneGroupName : 'default' - privateDnsZoneResourceIds: contains(privateEndpoint, 'privateDnsZoneResourceIds') ? privateEndpoint.privateDnsZoneResourceIds : [] - 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 : [] - applicationSecurityGroupResourceIds: contains(privateEndpoint, 'applicationSecurityGroupResourceIds') ? privateEndpoint.applicationSecurityGroupResourceIds : [] - customNetworkInterfaceName: contains(privateEndpoint, 'customNetworkInterfaceName') ? privateEndpoint.customNetworkInterfaceName : '' + privateDnsZoneGroupName: privateEndpoint.?privateDnsZoneGroupName + privateDnsZoneResourceIds: privateEndpoint.?privateDnsZoneResourceIds + roleAssignments: privateEndpoint.?roleAssignments + tags: privateEndpoint.?tags ?? tags + manualPrivateLinkServiceConnections: privateEndpoint.?manualPrivateLinkServiceConnections + customDnsConfigs: privateEndpoint.?customDnsConfigs + ipConfigurations: privateEndpoint.?ipConfigurations + applicationSecurityGroupResourceIds: privateEndpoint.?applicationSecurityGroupResourceIds + customNetworkInterfaceName: privateEndpoint.?customNetworkInterfaceName } }] @@ -396,3 +396,58 @@ type roleAssignmentType = { @description('Optional. The Resource Id of the delegated managed identity resource.') delegatedManagedIdentityResourceId: string? }[]? + +type privateEndpointType = { + @description('Optional. The name of the private endpoint.') + name: string? + + @description('Optional. The location to deploy the private endpoint to.') + location: string? + + @description('Optional. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob".') + service: string? + + @description('Required. Resource ID of the subnet where the endpoint needs to be created.') + subnetResourceId: string + + @description('Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided.') + privateDnsZoneGroupName: string? + + @description('Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones.') + privateDnsZoneResourceIds: string[]? + + @description('Optional. Custom DNS configurations.') + customDnsConfigs: { + fqdn: string? + ipAddresses: string[] + }[]? + + @description('Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints.') + ipConfigurations: { + name: string + groupId: string + memberName: string + privateIpAddress: string + }[]? + + @description('Optional. Application security groups in which the private endpoint IP configuration is included.') + applicationSecurityGroupResourceIds: string[]? + + @description('Optional. The custom name of the network interface attached to the private endpoint.') + customNetworkInterfaceName: string? + + @description('Optional. Specify the type of lock.') + lock: lockType + + @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\'.') + roleAssignments: roleAssignmentType + + @description('Optional. Tags to be applied on all resources/resource groups in this deployment.') + tags: object? + + @description('Optional. Manual PrivateLink Service Connections.') + manualPrivateLinkServiceConnections: array? + + @description('Optional. Enable/Disable usage telemetry for module.') + enableTelemetry: bool? +}[]? diff --git a/modules/cognitive-services/account/main.json b/modules/cognitive-services/account/main.json index 757da4d9aa..c2cff22d63 100644 --- a/modules/cognitive-services/account/main.json +++ b/modules/cognitive-services/account/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.22.6.54827", - "templateHash": "7200785404401861698" + "templateHash": "16169766026714928311" }, "name": "Cognitive Services", "description": "This module deploys a Cognitive Service.", @@ -103,6 +103,155 @@ } }, "nullable": true + }, + "privateEndpointType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private endpoint." + } + }, + "location": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The location to deploy the private endpoint to." + } + }, + "service": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The service (sub-) type to deploy the private endpoint for. For example \"vault\" or \"blob\"." + } + }, + "subnetResourceId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the subnet where the endpoint needs to be created." + } + }, + "privateDnsZoneGroupName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided." + } + }, + "privateDnsZoneResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones." + } + }, + "customDnsConfigs": { + "type": "array", + "items": { + "type": "object", + "properties": { + "fqdn": { + "type": "string", + "nullable": true + }, + "ipAddresses": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. Custom DNS configurations." + } + }, + "ipConfigurations": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "groupId": { + "type": "string" + }, + "memberName": { + "type": "string" + }, + "privateIpAddress": { + "type": "string" + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints." + } + }, + "applicationSecurityGroupResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. Application security groups in which the private endpoint IP configuration is included." + } + }, + "customNetworkInterfaceName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The custom name of the network interface attached to the private endpoint." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "metadata": { + "description": "Optional. Specify the type of lock." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "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'." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags to be applied on all resources/resource groups in this deployment." + } + }, + "manualPrivateLinkServiceConnections": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. Manual PrivateLink Service Connections." + } + }, + "enableTelemetry": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + } + } + }, + "nullable": true } }, "parameters": { @@ -230,8 +379,7 @@ } }, "privateEndpoints": { - "type": "array", - "defaultValue": [], + "$ref": "#/definitions/privateEndpointType", "metadata": { "description": "Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible." } @@ -583,11 +731,11 @@ "cognitiveServices_privateEndpoints": { "copy": { "name": "cognitiveServices_privateEndpoints", - "count": "[length(parameters('privateEndpoints'))]" + "count": "[length(coalesce(parameters('privateEndpoints'), createArray()))]" }, "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", - "name": "[format('{0}-CognitiveServices-PrivateEndpoint-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "name": "[format('{0}-cognitiveServices-PrivateEndpoint-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", "properties": { "expressionEvaluationOptions": { "scope": "inner" @@ -596,32 +744,54 @@ "parameters": { "groupIds": { "value": [ - "[parameters('privateEndpoints')[copyIndex()].service]" + "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'account')]" ] }, - "name": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'name'), createObject('value', parameters('privateEndpoints')[copyIndex()].name), createObject('value', format('pe-{0}-{1}-{2}', last(split(resourceId('Microsoft.CognitiveServices/accounts', parameters('name')), '/')), parameters('privateEndpoints')[copyIndex()].service, copyIndex())))]", + "name": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'name'), format('pep-{0}-{1}-{2}', last(split(resourceId('Microsoft.CognitiveServices/accounts', parameters('name')), '/')), coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'account'), copyIndex()))]" + }, "serviceResourceId": { "value": "[resourceId('Microsoft.CognitiveServices/accounts', parameters('name'))]" }, "subnetResourceId": { - "value": "[parameters('privateEndpoints')[copyIndex()].subnetResourceId]" + "value": "[coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId]" }, "enableDefaultTelemetry": { - "value": "[variables('enableReferencedModulesTelemetry')]" + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'enableDefaultTelemetry'), variables('enableReferencedModulesTelemetry'))]" + }, + "location": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'location'), reference(split(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location)]" }, - "location": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'location'), createObject('value', parameters('privateEndpoints')[copyIndex()].location), createObject('value', reference(split(parameters('privateEndpoints')[copyIndex()].subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location))]", "lock": { - "value": "[coalesce(tryGet(parameters('privateEndpoints')[copyIndex()], 'lock'), parameters('lock'))]" + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'lock'), parameters('lock'))]" + }, + "privateDnsZoneGroupName": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneGroupName')]" + }, + "privateDnsZoneResourceIds": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneResourceIds')]" + }, + "roleAssignments": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'roleAssignments')]" }, - "privateDnsZoneGroupName": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'privateDnsZoneGroupName'), createObject('value', parameters('privateEndpoints')[copyIndex()].privateDnsZoneGroupName), createObject('value', 'default'))]", - "privateDnsZoneResourceIds": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'privateDnsZoneResourceIds'), createObject('value', parameters('privateEndpoints')[copyIndex()].privateDnsZoneResourceIds), createObject('value', createArray()))]", - "roleAssignments": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'roleAssignments'), createObject('value', parameters('privateEndpoints')[copyIndex()].roleAssignments), createObject('value', createArray()))]", - "tags": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'tags'), createObject('value', parameters('privateEndpoints')[copyIndex()].tags), createObject('value', createObject()))]", - "manualPrivateLinkServiceConnections": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'manualPrivateLinkServiceConnections'), createObject('value', parameters('privateEndpoints')[copyIndex()].manualPrivateLinkServiceConnections), createObject('value', createArray()))]", - "customDnsConfigs": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'customDnsConfigs'), createObject('value', parameters('privateEndpoints')[copyIndex()].customDnsConfigs), createObject('value', createArray()))]", - "ipConfigurations": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'ipConfigurations'), createObject('value', parameters('privateEndpoints')[copyIndex()].ipConfigurations), createObject('value', createArray()))]", - "applicationSecurityGroupResourceIds": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'applicationSecurityGroupResourceIds'), createObject('value', parameters('privateEndpoints')[copyIndex()].applicationSecurityGroupResourceIds), createObject('value', createArray()))]", - "customNetworkInterfaceName": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'customNetworkInterfaceName'), createObject('value', parameters('privateEndpoints')[copyIndex()].customNetworkInterfaceName), createObject('value', ''))]" + "tags": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'tags'), parameters('tags'))]" + }, + "manualPrivateLinkServiceConnections": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'manualPrivateLinkServiceConnections')]" + }, + "customDnsConfigs": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customDnsConfigs')]" + }, + "ipConfigurations": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'ipConfigurations')]" + }, + "applicationSecurityGroupResourceIds": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'applicationSecurityGroupResourceIds')]" + }, + "customNetworkInterfaceName": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customNetworkInterfaceName')]" + } }, "template": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", diff --git a/modules/container-registry/registry/.test/pe/main.test.bicep b/modules/container-registry/registry/.test/pe/main.test.bicep index a0708497ad..f3f4cf3339 100644 --- a/modules/container-registry/registry/.test/pe/main.test.bicep +++ b/modules/container-registry/registry/.test/pe/main.test.bicep @@ -52,7 +52,6 @@ module testDeployment '../../main.bicep' = { acrSku: 'Premium' privateEndpoints: [ { - service: 'registry' subnetResourceId: nestedDependencies.outputs.subnetResourceId privateDnsZoneResourceIds: [ nestedDependencies.outputs.privateDNSZoneResourceId diff --git a/modules/container-registry/registry/README.md b/modules/container-registry/registry/README.md index a538ee678a..c2daee1c97 100644 --- a/modules/container-registry/registry/README.md +++ b/modules/container-registry/registry/README.md @@ -430,7 +430,6 @@ module registry 'br:bicep/modules/container-registry.registry:1.0.0' = { privateDnsZoneResourceIds: [ '' ] - service: 'registry' subnetResourceId: '' tags: { Environment: 'Non-Prod' @@ -477,7 +476,6 @@ module registry 'br:bicep/modules/container-registry.registry:1.0.0' = { "privateDnsZoneResourceIds": [ "" ], - "service": "registry", "subnetResourceId": "", "tags": { "Environment": "Non-Prod", @@ -765,7 +763,168 @@ The IP ACL rules. Note, requires the 'acrSku' to be 'Premium'. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible. Note, requires the 'acrSku' to be 'Premium'. - Required: No - Type: array -- Default: `[]` + + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`applicationSecurityGroupResourceIds`](#parameter-privateendpointsapplicationsecuritygroupresourceids) | No | array | Optional. Application security groups in which the private endpoint IP configuration is included. | +| [`customDnsConfigs`](#parameter-privateendpointscustomdnsconfigs) | No | array | Optional. Custom DNS configurations. | +| [`customNetworkInterfaceName`](#parameter-privateendpointscustomnetworkinterfacename) | No | string | Optional. The custom name of the network interface attached to the private endpoint. | +| [`enableTelemetry`](#parameter-privateendpointsenabletelemetry) | No | bool | Optional. Enable/Disable usage telemetry for module. | +| [`ipConfigurations`](#parameter-privateendpointsipconfigurations) | No | array | Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints. | +| [`location`](#parameter-privateendpointslocation) | No | string | Optional. The location to deploy the private endpoint to. | +| [`lock`](#parameter-privateendpointslock) | No | object | Optional. Specify the type of lock. | +| [`manualPrivateLinkServiceConnections`](#parameter-privateendpointsmanualprivatelinkserviceconnections) | No | array | Optional. Manual PrivateLink Service Connections. | +| [`name`](#parameter-privateendpointsname) | No | string | Optional. The name of the private endpoint. | +| [`privateDnsZoneGroupName`](#parameter-privateendpointsprivatednszonegroupname) | No | string | Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided. | +| [`privateDnsZoneResourceIds`](#parameter-privateendpointsprivatednszoneresourceids) | No | array | Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones. | +| [`roleAssignments`](#parameter-privateendpointsroleassignments) | No | array | 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'. | +| [`service`](#parameter-privateendpointsservice) | No | string | Optional. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob". | +| [`subnetResourceId`](#parameter-privateendpointssubnetresourceid) | Yes | string | Required. Resource ID of the subnet where the endpoint needs to be created. | +| [`tags`](#parameter-privateendpointstags) | No | object | Optional. Tags to be applied on all resources/resource groups in this deployment. | + +### Parameter: `privateEndpoints.applicationSecurityGroupResourceIds` + +Optional. Application security groups in which the private endpoint IP configuration is included. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.customDnsConfigs` + +Optional. Custom DNS configurations. + +- Required: No +- Type: array + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`fqdn`](#parameter-privateendpointscustomdnsconfigsfqdn) | No | string | | +| [`ipAddresses`](#parameter-privateendpointscustomdnsconfigsipaddresses) | Yes | array | | + +### Parameter: `privateEndpoints.customDnsConfigs.fqdn` +- Required: No +- Type: string + +### Parameter: `privateEndpoints.customDnsConfigs.ipAddresses` +- Required: Yes +- Type: array + + +### Parameter: `privateEndpoints.customNetworkInterfaceName` + +Optional. The custom name of the network interface attached to the private endpoint. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.enableTelemetry` + +Optional. Enable/Disable usage telemetry for module. + +- Required: No +- Type: bool + +### Parameter: `privateEndpoints.ipConfigurations` + +Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints. + +- Required: No +- Type: array + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`groupId`](#parameter-privateendpointsipconfigurationsgroupid) | Yes | string | | +| [`memberName`](#parameter-privateendpointsipconfigurationsmembername) | Yes | string | | +| [`name`](#parameter-privateendpointsipconfigurationsname) | Yes | string | | +| [`privateIpAddress`](#parameter-privateendpointsipconfigurationsprivateipaddress) | Yes | string | | + +### Parameter: `privateEndpoints.ipConfigurations.groupId` +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.ipConfigurations.memberName` +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.ipConfigurations.name` +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.ipConfigurations.privateIpAddress` +- Required: Yes +- Type: string + + +### Parameter: `privateEndpoints.location` + +Optional. The location to deploy the private endpoint to. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.lock` + +Optional. Specify the type of lock. + +- Required: No +- Type: object + +### Parameter: `privateEndpoints.manualPrivateLinkServiceConnections` + +Optional. Manual PrivateLink Service Connections. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.name` + +Optional. The name of the private endpoint. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.privateDnsZoneGroupName` + +Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.privateDnsZoneResourceIds` + +Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.roleAssignments` + +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'. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.service` + +Optional. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob". + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.subnetResourceId` + +Required. Resource ID of the subnet where the endpoint needs to be created. + +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.tags` + +Optional. Tags to be applied on all resources/resource groups in this deployment. + +- Required: No +- Type: object ### Parameter: `publicNetworkAccess` diff --git a/modules/container-registry/registry/main.bicep b/modules/container-registry/registry/main.bicep index 6cd474fcec..373ad0d281 100644 --- a/modules/container-registry/registry/main.bicep +++ b/modules/container-registry/registry/main.bicep @@ -101,7 +101,7 @@ param networkRuleSetDefaultAction string = 'Deny' param networkRuleSetIpRules array = [] @description('Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible. Note, requires the \'acrSku\' to be \'Premium\'.') -param privateEndpoints array = [] +param privateEndpoints privateEndpointType @allowed([ 'Disabled' @@ -385,27 +385,27 @@ resource registry_roleAssignments 'Microsoft.Authorization/roleAssignments@2022- scope: registry }] -module registry_privateEndpoints '../../network/private-endpoint/main.bicep' = [for (privateEndpoint, index) in privateEndpoints: { - name: '${uniqueString(deployment().name, location)}-ContainerRegistry-PrivateEndpoint-${index}' +module registry_privateEndpoints '../../network/private-endpoint/main.bicep' = [for (privateEndpoint, index) in (privateEndpoints ?? []): { + name: '${uniqueString(deployment().name, location)}-registry-PrivateEndpoint-${index}' params: { groupIds: [ - privateEndpoint.service + privateEndpoint.?service ?? 'registry' ] - name: contains(privateEndpoint, 'name') ? privateEndpoint.name : 'pe-${last(split(registry.id, '/'))}-${privateEndpoint.service}-${index}' + name: privateEndpoint.?name ?? 'pep-${last(split(registry.id, '/'))}-${privateEndpoint.?service ?? 'registry'}-${index}' serviceResourceId: registry.id subnetResourceId: privateEndpoint.subnetResourceId - enableDefaultTelemetry: enableReferencedModulesTelemetry - location: contains(privateEndpoint, 'location') ? privateEndpoint.location : reference(split(privateEndpoint.subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location + enableDefaultTelemetry: privateEndpoint.?enableDefaultTelemetry ?? enableReferencedModulesTelemetry + location: privateEndpoint.?location ?? reference(split(privateEndpoint.subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location lock: privateEndpoint.?lock ?? lock - privateDnsZoneGroupName: contains(privateEndpoint, 'privateDnsZoneGroupName') ? privateEndpoint.privateDnsZoneGroupName : 'default' - privateDnsZoneResourceIds: contains(privateEndpoint, 'privateDnsZoneResourceIds') ? privateEndpoint.privateDnsZoneResourceIds : [] - 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 : [] - applicationSecurityGroupResourceIds: contains(privateEndpoint, 'applicationSecurityGroupResourceIds') ? privateEndpoint.applicationSecurityGroupResourceIds : [] - customNetworkInterfaceName: contains(privateEndpoint, 'customNetworkInterfaceName') ? privateEndpoint.customNetworkInterfaceName : '' + privateDnsZoneGroupName: privateEndpoint.?privateDnsZoneGroupName + privateDnsZoneResourceIds: privateEndpoint.?privateDnsZoneResourceIds + roleAssignments: privateEndpoint.?roleAssignments + tags: privateEndpoint.?tags ?? tags + manualPrivateLinkServiceConnections: privateEndpoint.?manualPrivateLinkServiceConnections + customDnsConfigs: privateEndpoint.?customDnsConfigs + ipConfigurations: privateEndpoint.?ipConfigurations + applicationSecurityGroupResourceIds: privateEndpoint.?applicationSecurityGroupResourceIds + customNetworkInterfaceName: privateEndpoint.?customNetworkInterfaceName } }] @@ -461,3 +461,58 @@ type roleAssignmentType = { @description('Optional. The Resource Id of the delegated managed identity resource.') delegatedManagedIdentityResourceId: string? }[]? + +type privateEndpointType = { + @description('Optional. The name of the private endpoint.') + name: string? + + @description('Optional. The location to deploy the private endpoint to.') + location: string? + + @description('Optional. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob".') + service: string? + + @description('Required. Resource ID of the subnet where the endpoint needs to be created.') + subnetResourceId: string + + @description('Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided.') + privateDnsZoneGroupName: string? + + @description('Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones.') + privateDnsZoneResourceIds: string[]? + + @description('Optional. Custom DNS configurations.') + customDnsConfigs: { + fqdn: string? + ipAddresses: string[] + }[]? + + @description('Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints.') + ipConfigurations: { + name: string + groupId: string + memberName: string + privateIpAddress: string + }[]? + + @description('Optional. Application security groups in which the private endpoint IP configuration is included.') + applicationSecurityGroupResourceIds: string[]? + + @description('Optional. The custom name of the network interface attached to the private endpoint.') + customNetworkInterfaceName: string? + + @description('Optional. Specify the type of lock.') + lock: lockType + + @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\'.') + roleAssignments: roleAssignmentType + + @description('Optional. Tags to be applied on all resources/resource groups in this deployment.') + tags: object? + + @description('Optional. Manual PrivateLink Service Connections.') + manualPrivateLinkServiceConnections: array? + + @description('Optional. Enable/Disable usage telemetry for module.') + enableTelemetry: bool? +}[]? diff --git a/modules/container-registry/registry/main.json b/modules/container-registry/registry/main.json index d643bb198c..22da0543a6 100644 --- a/modules/container-registry/registry/main.json +++ b/modules/container-registry/registry/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.22.6.54827", - "templateHash": "1785285011964376463" + "templateHash": "13701712585217566427" }, "name": "Azure Container Registries (ACR)", "description": "This module deploys an Azure Container Registry (ACR).", @@ -103,6 +103,155 @@ } }, "nullable": true + }, + "privateEndpointType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private endpoint." + } + }, + "location": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The location to deploy the private endpoint to." + } + }, + "service": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The service (sub-) type to deploy the private endpoint for. For example \"vault\" or \"blob\"." + } + }, + "subnetResourceId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the subnet where the endpoint needs to be created." + } + }, + "privateDnsZoneGroupName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided." + } + }, + "privateDnsZoneResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones." + } + }, + "customDnsConfigs": { + "type": "array", + "items": { + "type": "object", + "properties": { + "fqdn": { + "type": "string", + "nullable": true + }, + "ipAddresses": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. Custom DNS configurations." + } + }, + "ipConfigurations": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "groupId": { + "type": "string" + }, + "memberName": { + "type": "string" + }, + "privateIpAddress": { + "type": "string" + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints." + } + }, + "applicationSecurityGroupResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. Application security groups in which the private endpoint IP configuration is included." + } + }, + "customNetworkInterfaceName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The custom name of the network interface attached to the private endpoint." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "metadata": { + "description": "Optional. Specify the type of lock." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "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'." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags to be applied on all resources/resource groups in this deployment." + } + }, + "manualPrivateLinkServiceConnections": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. Manual PrivateLink Service Connections." + } + }, + "enableTelemetry": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + } + } + }, + "nullable": true } }, "parameters": { @@ -275,8 +424,7 @@ } }, "privateEndpoints": { - "type": "array", - "defaultValue": [], + "$ref": "#/definitions/privateEndpointType", "metadata": { "description": "Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible. Note, requires the 'acrSku' to be 'Premium'." } @@ -1139,11 +1287,11 @@ "registry_privateEndpoints": { "copy": { "name": "registry_privateEndpoints", - "count": "[length(parameters('privateEndpoints'))]" + "count": "[length(coalesce(parameters('privateEndpoints'), createArray()))]" }, "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", - "name": "[format('{0}-ContainerRegistry-PrivateEndpoint-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "name": "[format('{0}-registry-PrivateEndpoint-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", "properties": { "expressionEvaluationOptions": { "scope": "inner" @@ -1152,32 +1300,54 @@ "parameters": { "groupIds": { "value": [ - "[parameters('privateEndpoints')[copyIndex()].service]" + "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'registry')]" ] }, - "name": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'name'), createObject('value', parameters('privateEndpoints')[copyIndex()].name), createObject('value', format('pe-{0}-{1}-{2}', last(split(resourceId('Microsoft.ContainerRegistry/registries', parameters('name')), '/')), parameters('privateEndpoints')[copyIndex()].service, copyIndex())))]", + "name": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'name'), format('pep-{0}-{1}-{2}', last(split(resourceId('Microsoft.ContainerRegistry/registries', parameters('name')), '/')), coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'registry'), copyIndex()))]" + }, "serviceResourceId": { "value": "[resourceId('Microsoft.ContainerRegistry/registries', parameters('name'))]" }, "subnetResourceId": { - "value": "[parameters('privateEndpoints')[copyIndex()].subnetResourceId]" + "value": "[coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId]" }, "enableDefaultTelemetry": { - "value": "[variables('enableReferencedModulesTelemetry')]" + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'enableDefaultTelemetry'), variables('enableReferencedModulesTelemetry'))]" + }, + "location": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'location'), reference(split(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location)]" }, - "location": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'location'), createObject('value', parameters('privateEndpoints')[copyIndex()].location), createObject('value', reference(split(parameters('privateEndpoints')[copyIndex()].subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location))]", "lock": { - "value": "[coalesce(tryGet(parameters('privateEndpoints')[copyIndex()], 'lock'), parameters('lock'))]" + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'lock'), parameters('lock'))]" + }, + "privateDnsZoneGroupName": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneGroupName')]" + }, + "privateDnsZoneResourceIds": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneResourceIds')]" + }, + "roleAssignments": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'roleAssignments')]" }, - "privateDnsZoneGroupName": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'privateDnsZoneGroupName'), createObject('value', parameters('privateEndpoints')[copyIndex()].privateDnsZoneGroupName), createObject('value', 'default'))]", - "privateDnsZoneResourceIds": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'privateDnsZoneResourceIds'), createObject('value', parameters('privateEndpoints')[copyIndex()].privateDnsZoneResourceIds), createObject('value', createArray()))]", - "roleAssignments": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'roleAssignments'), createObject('value', parameters('privateEndpoints')[copyIndex()].roleAssignments), createObject('value', createArray()))]", - "tags": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'tags'), createObject('value', parameters('privateEndpoints')[copyIndex()].tags), createObject('value', createObject()))]", - "manualPrivateLinkServiceConnections": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'manualPrivateLinkServiceConnections'), createObject('value', parameters('privateEndpoints')[copyIndex()].manualPrivateLinkServiceConnections), createObject('value', createArray()))]", - "customDnsConfigs": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'customDnsConfigs'), createObject('value', parameters('privateEndpoints')[copyIndex()].customDnsConfigs), createObject('value', createArray()))]", - "ipConfigurations": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'ipConfigurations'), createObject('value', parameters('privateEndpoints')[copyIndex()].ipConfigurations), createObject('value', createArray()))]", - "applicationSecurityGroupResourceIds": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'applicationSecurityGroupResourceIds'), createObject('value', parameters('privateEndpoints')[copyIndex()].applicationSecurityGroupResourceIds), createObject('value', createArray()))]", - "customNetworkInterfaceName": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'customNetworkInterfaceName'), createObject('value', parameters('privateEndpoints')[copyIndex()].customNetworkInterfaceName), createObject('value', ''))]" + "tags": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'tags'), parameters('tags'))]" + }, + "manualPrivateLinkServiceConnections": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'manualPrivateLinkServiceConnections')]" + }, + "customDnsConfigs": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customDnsConfigs')]" + }, + "ipConfigurations": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'ipConfigurations')]" + }, + "applicationSecurityGroupResourceIds": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'applicationSecurityGroupResourceIds')]" + }, + "customNetworkInterfaceName": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customNetworkInterfaceName')]" + } }, "template": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", diff --git a/modules/data-factory/factory/.test/common/main.test.bicep b/modules/data-factory/factory/.test/common/main.test.bicep index 1fae6339c0..c0692ca43a 100644 --- a/modules/data-factory/factory/.test/common/main.test.bicep +++ b/modules/data-factory/factory/.test/common/main.test.bicep @@ -118,11 +118,8 @@ module testDeployment '../../main.bicep' = { privateEndpoints: [ { privateDnsZoneResourceIds: [ - nestedDependencies.outputs.privateDNSZoneResourceId - ] - service: 'dataFactory' subnetResourceId: nestedDependencies.outputs.subnetResourceId tags: { 'hidden-title': 'This is visible in the resource name' diff --git a/modules/data-factory/factory/README.md b/modules/data-factory/factory/README.md index 6ae177c9e1..82283fdb1c 100644 --- a/modules/data-factory/factory/README.md +++ b/modules/data-factory/factory/README.md @@ -103,7 +103,6 @@ module factory 'br:bicep/modules/data-factory.factory:1.0.0' = { privateDnsZoneResourceIds: [ '' ] - service: 'dataFactory' subnetResourceId: '' tags: { application: 'CARML' @@ -228,7 +227,6 @@ module factory 'br:bicep/modules/data-factory.factory:1.0.0' = { "privateDnsZoneResourceIds": [ "" ], - "service": "dataFactory", "subnetResourceId": "", "tags": { "application": "CARML", @@ -590,7 +588,168 @@ The name of the Azure Factory to create. Configuration Details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible. - Required: No - Type: array -- Default: `[]` + + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`applicationSecurityGroupResourceIds`](#parameter-privateendpointsapplicationsecuritygroupresourceids) | No | array | Optional. Application security groups in which the private endpoint IP configuration is included. | +| [`customDnsConfigs`](#parameter-privateendpointscustomdnsconfigs) | No | array | Optional. Custom DNS configurations. | +| [`customNetworkInterfaceName`](#parameter-privateendpointscustomnetworkinterfacename) | No | string | Optional. The custom name of the network interface attached to the private endpoint. | +| [`enableTelemetry`](#parameter-privateendpointsenabletelemetry) | No | bool | Optional. Enable/Disable usage telemetry for module. | +| [`ipConfigurations`](#parameter-privateendpointsipconfigurations) | No | array | Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints. | +| [`location`](#parameter-privateendpointslocation) | No | string | Optional. The location to deploy the private endpoint to. | +| [`lock`](#parameter-privateendpointslock) | No | object | Optional. Specify the type of lock. | +| [`manualPrivateLinkServiceConnections`](#parameter-privateendpointsmanualprivatelinkserviceconnections) | No | array | Optional. Manual PrivateLink Service Connections. | +| [`name`](#parameter-privateendpointsname) | No | string | Optional. The name of the private endpoint. | +| [`privateDnsZoneGroupName`](#parameter-privateendpointsprivatednszonegroupname) | No | string | Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided. | +| [`privateDnsZoneResourceIds`](#parameter-privateendpointsprivatednszoneresourceids) | No | array | Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones. | +| [`roleAssignments`](#parameter-privateendpointsroleassignments) | No | array | 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'. | +| [`service`](#parameter-privateendpointsservice) | No | string | Optional. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob". | +| [`subnetResourceId`](#parameter-privateendpointssubnetresourceid) | Yes | string | Required. Resource ID of the subnet where the endpoint needs to be created. | +| [`tags`](#parameter-privateendpointstags) | No | object | Optional. Tags to be applied on all resources/resource groups in this deployment. | + +### Parameter: `privateEndpoints.applicationSecurityGroupResourceIds` + +Optional. Application security groups in which the private endpoint IP configuration is included. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.customDnsConfigs` + +Optional. Custom DNS configurations. + +- Required: No +- Type: array + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`fqdn`](#parameter-privateendpointscustomdnsconfigsfqdn) | No | string | | +| [`ipAddresses`](#parameter-privateendpointscustomdnsconfigsipaddresses) | Yes | array | | + +### Parameter: `privateEndpoints.customDnsConfigs.fqdn` +- Required: No +- Type: string + +### Parameter: `privateEndpoints.customDnsConfigs.ipAddresses` +- Required: Yes +- Type: array + + +### Parameter: `privateEndpoints.customNetworkInterfaceName` + +Optional. The custom name of the network interface attached to the private endpoint. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.enableTelemetry` + +Optional. Enable/Disable usage telemetry for module. + +- Required: No +- Type: bool + +### Parameter: `privateEndpoints.ipConfigurations` + +Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints. + +- Required: No +- Type: array + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`groupId`](#parameter-privateendpointsipconfigurationsgroupid) | Yes | string | | +| [`memberName`](#parameter-privateendpointsipconfigurationsmembername) | Yes | string | | +| [`name`](#parameter-privateendpointsipconfigurationsname) | Yes | string | | +| [`privateIpAddress`](#parameter-privateendpointsipconfigurationsprivateipaddress) | Yes | string | | + +### Parameter: `privateEndpoints.ipConfigurations.groupId` +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.ipConfigurations.memberName` +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.ipConfigurations.name` +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.ipConfigurations.privateIpAddress` +- Required: Yes +- Type: string + + +### Parameter: `privateEndpoints.location` + +Optional. The location to deploy the private endpoint to. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.lock` + +Optional. Specify the type of lock. + +- Required: No +- Type: object + +### Parameter: `privateEndpoints.manualPrivateLinkServiceConnections` + +Optional. Manual PrivateLink Service Connections. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.name` + +Optional. The name of the private endpoint. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.privateDnsZoneGroupName` + +Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.privateDnsZoneResourceIds` + +Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.roleAssignments` + +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'. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.service` + +Optional. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob". + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.subnetResourceId` + +Required. Resource ID of the subnet where the endpoint needs to be created. + +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.tags` + +Optional. Tags to be applied on all resources/resource groups in this deployment. + +- Required: No +- Type: object ### Parameter: `publicNetworkAccess` diff --git a/modules/data-factory/factory/main.bicep b/modules/data-factory/factory/main.bicep index c90d4d9801..ead4706f37 100644 --- a/modules/data-factory/factory/main.bicep +++ b/modules/data-factory/factory/main.bicep @@ -77,7 +77,7 @@ param systemAssignedIdentity bool = false param userAssignedIdentities object = {} @description('Optional. Configuration Details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible.') -param privateEndpoints array = [] +param privateEndpoints privateEndpointType @description('Conditional. The resource ID of a key vault to reference a customer managed key for encryption from. Required if \'cMKKeyName\' is not empty.') param cMKKeyVaultResourceId string = '' @@ -273,27 +273,27 @@ resource dataFactory_roleAssignments 'Microsoft.Authorization/roleAssignments@20 scope: dataFactory }] -module dataFactory_privateEndpoints '../../network/private-endpoint/main.bicep' = [for (privateEndpoint, index) in privateEndpoints: { - name: '${uniqueString(deployment().name, location)}-DataFactory-PrivateEndpoint-${index}' +module dataFactory_privateEndpoints '../../network/private-endpoint/main.bicep' = [for (privateEndpoint, index) in (privateEndpoints ?? []): { + name: '${uniqueString(deployment().name, location)}-dataFactory-PrivateEndpoint-${index}' params: { groupIds: [ - privateEndpoint.service + privateEndpoint.?service ?? 'dataFactory' ] - name: contains(privateEndpoint, 'name') ? privateEndpoint.name : 'pe-${last(split(dataFactory.id, '/'))}-${privateEndpoint.service}-${index}' + name: privateEndpoint.?name ?? 'pep-${last(split(dataFactory.id, '/'))}-${privateEndpoint.?service ?? 'dataFactory'}-${index}' serviceResourceId: dataFactory.id subnetResourceId: privateEndpoint.subnetResourceId - enableDefaultTelemetry: enableReferencedModulesTelemetry - location: contains(privateEndpoint, 'location') ? privateEndpoint.location : reference(split(privateEndpoint.subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location + enableDefaultTelemetry: privateEndpoint.?enableDefaultTelemetry ?? enableReferencedModulesTelemetry + location: privateEndpoint.?location ?? reference(split(privateEndpoint.subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location lock: privateEndpoint.?lock ?? lock - privateDnsZoneGroupName: contains(privateEndpoint, 'privateDnsZoneGroupName') ? privateEndpoint.privateDnsZoneGroupName : 'default' - privateDnsZoneResourceIds: contains(privateEndpoint, 'privateDnsZoneResourceIds') ? privateEndpoint.privateDnsZoneResourceIds : [] - 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 : [] - applicationSecurityGroupResourceIds: contains(privateEndpoint, 'applicationSecurityGroupResourceIds') ? privateEndpoint.applicationSecurityGroupResourceIds : [] - customNetworkInterfaceName: contains(privateEndpoint, 'customNetworkInterfaceName') ? privateEndpoint.customNetworkInterfaceName : '' + privateDnsZoneGroupName: privateEndpoint.?privateDnsZoneGroupName + privateDnsZoneResourceIds: privateEndpoint.?privateDnsZoneResourceIds + roleAssignments: privateEndpoint.?roleAssignments + tags: privateEndpoint.?tags ?? tags + manualPrivateLinkServiceConnections: privateEndpoint.?manualPrivateLinkServiceConnections + customDnsConfigs: privateEndpoint.?customDnsConfigs + ipConfigurations: privateEndpoint.?ipConfigurations + applicationSecurityGroupResourceIds: privateEndpoint.?applicationSecurityGroupResourceIds + customNetworkInterfaceName: privateEndpoint.?customNetworkInterfaceName } }] @@ -346,3 +346,58 @@ type roleAssignmentType = { @description('Optional. The Resource Id of the delegated managed identity resource.') delegatedManagedIdentityResourceId: string? }[]? + +type privateEndpointType = { + @description('Optional. The name of the private endpoint.') + name: string? + + @description('Optional. The location to deploy the private endpoint to.') + location: string? + + @description('Optional. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob".') + service: string? + + @description('Required. Resource ID of the subnet where the endpoint needs to be created.') + subnetResourceId: string + + @description('Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided.') + privateDnsZoneGroupName: string? + + @description('Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones.') + privateDnsZoneResourceIds: string[]? + + @description('Optional. Custom DNS configurations.') + customDnsConfigs: { + fqdn: string? + ipAddresses: string[] + }[]? + + @description('Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints.') + ipConfigurations: { + name: string + groupId: string + memberName: string + privateIpAddress: string + }[]? + + @description('Optional. Application security groups in which the private endpoint IP configuration is included.') + applicationSecurityGroupResourceIds: string[]? + + @description('Optional. The custom name of the network interface attached to the private endpoint.') + customNetworkInterfaceName: string? + + @description('Optional. Specify the type of lock.') + lock: lockType + + @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\'.') + roleAssignments: roleAssignmentType + + @description('Optional. Tags to be applied on all resources/resource groups in this deployment.') + tags: object? + + @description('Optional. Manual PrivateLink Service Connections.') + manualPrivateLinkServiceConnections: array? + + @description('Optional. Enable/Disable usage telemetry for module.') + enableTelemetry: bool? +}[]? diff --git a/modules/data-factory/factory/main.json b/modules/data-factory/factory/main.json index 1213204e82..3537a59a94 100644 --- a/modules/data-factory/factory/main.json +++ b/modules/data-factory/factory/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.22.6.54827", - "templateHash": "6726222528334503492" + "templateHash": "3087206117365778401" }, "name": "Data Factories", "description": "This module deploys a Data Factory.", @@ -103,6 +103,155 @@ } }, "nullable": true + }, + "privateEndpointType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private endpoint." + } + }, + "location": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The location to deploy the private endpoint to." + } + }, + "service": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The service (sub-) type to deploy the private endpoint for. For example \"vault\" or \"blob\"." + } + }, + "subnetResourceId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the subnet where the endpoint needs to be created." + } + }, + "privateDnsZoneGroupName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided." + } + }, + "privateDnsZoneResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones." + } + }, + "customDnsConfigs": { + "type": "array", + "items": { + "type": "object", + "properties": { + "fqdn": { + "type": "string", + "nullable": true + }, + "ipAddresses": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. Custom DNS configurations." + } + }, + "ipConfigurations": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "groupId": { + "type": "string" + }, + "memberName": { + "type": "string" + }, + "privateIpAddress": { + "type": "string" + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints." + } + }, + "applicationSecurityGroupResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. Application security groups in which the private endpoint IP configuration is included." + } + }, + "customNetworkInterfaceName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The custom name of the network interface attached to the private endpoint." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "metadata": { + "description": "Optional. Specify the type of lock." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "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'." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags to be applied on all resources/resource groups in this deployment." + } + }, + "manualPrivateLinkServiceConnections": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. Manual PrivateLink Service Connections." + } + }, + "enableTelemetry": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + } + } + }, + "nullable": true } }, "parameters": { @@ -271,8 +420,7 @@ } }, "privateEndpoints": { - "type": "array", - "defaultValue": [], + "$ref": "#/definitions/privateEndpointType", "metadata": { "description": "Optional. Configuration Details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible." } @@ -908,11 +1056,11 @@ "dataFactory_privateEndpoints": { "copy": { "name": "dataFactory_privateEndpoints", - "count": "[length(parameters('privateEndpoints'))]" + "count": "[length(coalesce(parameters('privateEndpoints'), createArray()))]" }, "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", - "name": "[format('{0}-DataFactory-PrivateEndpoint-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "name": "[format('{0}-dataFactory-PrivateEndpoint-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", "properties": { "expressionEvaluationOptions": { "scope": "inner" @@ -921,32 +1069,54 @@ "parameters": { "groupIds": { "value": [ - "[parameters('privateEndpoints')[copyIndex()].service]" + "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'dataFactory')]" ] }, - "name": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'name'), createObject('value', parameters('privateEndpoints')[copyIndex()].name), createObject('value', format('pe-{0}-{1}-{2}', last(split(resourceId('Microsoft.DataFactory/factories', parameters('name')), '/')), parameters('privateEndpoints')[copyIndex()].service, copyIndex())))]", + "name": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'name'), format('pep-{0}-{1}-{2}', last(split(resourceId('Microsoft.DataFactory/factories', parameters('name')), '/')), coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'dataFactory'), copyIndex()))]" + }, "serviceResourceId": { "value": "[resourceId('Microsoft.DataFactory/factories', parameters('name'))]" }, "subnetResourceId": { - "value": "[parameters('privateEndpoints')[copyIndex()].subnetResourceId]" + "value": "[coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId]" }, "enableDefaultTelemetry": { - "value": "[variables('enableReferencedModulesTelemetry')]" + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'enableDefaultTelemetry'), variables('enableReferencedModulesTelemetry'))]" + }, + "location": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'location'), reference(split(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location)]" }, - "location": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'location'), createObject('value', parameters('privateEndpoints')[copyIndex()].location), createObject('value', reference(split(parameters('privateEndpoints')[copyIndex()].subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location))]", "lock": { - "value": "[coalesce(tryGet(parameters('privateEndpoints')[copyIndex()], 'lock'), parameters('lock'))]" + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'lock'), parameters('lock'))]" + }, + "privateDnsZoneGroupName": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneGroupName')]" + }, + "privateDnsZoneResourceIds": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneResourceIds')]" + }, + "roleAssignments": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'roleAssignments')]" }, - "privateDnsZoneGroupName": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'privateDnsZoneGroupName'), createObject('value', parameters('privateEndpoints')[copyIndex()].privateDnsZoneGroupName), createObject('value', 'default'))]", - "privateDnsZoneResourceIds": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'privateDnsZoneResourceIds'), createObject('value', parameters('privateEndpoints')[copyIndex()].privateDnsZoneResourceIds), createObject('value', createArray()))]", - "roleAssignments": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'roleAssignments'), createObject('value', parameters('privateEndpoints')[copyIndex()].roleAssignments), createObject('value', createArray()))]", - "tags": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'tags'), createObject('value', parameters('privateEndpoints')[copyIndex()].tags), createObject('value', createObject()))]", - "manualPrivateLinkServiceConnections": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'manualPrivateLinkServiceConnections'), createObject('value', parameters('privateEndpoints')[copyIndex()].manualPrivateLinkServiceConnections), createObject('value', createArray()))]", - "customDnsConfigs": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'customDnsConfigs'), createObject('value', parameters('privateEndpoints')[copyIndex()].customDnsConfigs), createObject('value', createArray()))]", - "ipConfigurations": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'ipConfigurations'), createObject('value', parameters('privateEndpoints')[copyIndex()].ipConfigurations), createObject('value', createArray()))]", - "applicationSecurityGroupResourceIds": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'applicationSecurityGroupResourceIds'), createObject('value', parameters('privateEndpoints')[copyIndex()].applicationSecurityGroupResourceIds), createObject('value', createArray()))]", - "customNetworkInterfaceName": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'customNetworkInterfaceName'), createObject('value', parameters('privateEndpoints')[copyIndex()].customNetworkInterfaceName), createObject('value', ''))]" + "tags": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'tags'), parameters('tags'))]" + }, + "manualPrivateLinkServiceConnections": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'manualPrivateLinkServiceConnections')]" + }, + "customDnsConfigs": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customDnsConfigs')]" + }, + "ipConfigurations": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'ipConfigurations')]" + }, + "applicationSecurityGroupResourceIds": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'applicationSecurityGroupResourceIds')]" + }, + "customNetworkInterfaceName": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customNetworkInterfaceName')]" + } }, "template": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", diff --git a/modules/databricks/workspace/.test/common/main.test.bicep b/modules/databricks/workspace/.test/common/main.test.bicep index 756379bda5..d007056101 100644 --- a/modules/databricks/workspace/.test/common/main.test.bicep +++ b/modules/databricks/workspace/.test/common/main.test.bicep @@ -121,11 +121,8 @@ module testDeployment '../../main.bicep' = { privateEndpoints: [ { privateDnsZoneResourceIds: [ - nestedDependencies.outputs.privateDNSZoneResourceId - ] - service: 'databricks_ui_api' subnetResourceId: nestedDependencies.outputs.defaultSubnetResourceId tags: { Environment: 'Non-Prod' diff --git a/modules/databricks/workspace/README.md b/modules/databricks/workspace/README.md index 7b1bdd1cb9..74096d1456 100644 --- a/modules/databricks/workspace/README.md +++ b/modules/databricks/workspace/README.md @@ -84,7 +84,6 @@ module workspace 'br:bicep/modules/databricks.workspace:1.0.0' = { privateDnsZoneResourceIds: [ '' ] - service: 'databricks_ui_api' subnetResourceId: '' tags: { Environment: 'Non-Prod' @@ -217,7 +216,6 @@ module workspace 'br:bicep/modules/databricks.workspace:1.0.0' = { "privateDnsZoneResourceIds": [ "" ], - "service": "databricks_ui_api", "subnetResourceId": "", "tags": { "Environment": "Non-Prod", @@ -591,7 +589,168 @@ Prepare the workspace for encryption. Enables the Managed Identity for managed s Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible. - Required: No - Type: array -- Default: `[]` + + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`applicationSecurityGroupResourceIds`](#parameter-privateendpointsapplicationsecuritygroupresourceids) | No | array | Optional. Application security groups in which the private endpoint IP configuration is included. | +| [`customDnsConfigs`](#parameter-privateendpointscustomdnsconfigs) | No | array | Optional. Custom DNS configurations. | +| [`customNetworkInterfaceName`](#parameter-privateendpointscustomnetworkinterfacename) | No | string | Optional. The custom name of the network interface attached to the private endpoint. | +| [`enableTelemetry`](#parameter-privateendpointsenabletelemetry) | No | bool | Optional. Enable/Disable usage telemetry for module. | +| [`ipConfigurations`](#parameter-privateendpointsipconfigurations) | No | array | Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints. | +| [`location`](#parameter-privateendpointslocation) | No | string | Optional. The location to deploy the private endpoint to. | +| [`lock`](#parameter-privateendpointslock) | No | object | Optional. Specify the type of lock. | +| [`manualPrivateLinkServiceConnections`](#parameter-privateendpointsmanualprivatelinkserviceconnections) | No | array | Optional. Manual PrivateLink Service Connections. | +| [`name`](#parameter-privateendpointsname) | No | string | Optional. The name of the private endpoint. | +| [`privateDnsZoneGroupName`](#parameter-privateendpointsprivatednszonegroupname) | No | string | Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided. | +| [`privateDnsZoneResourceIds`](#parameter-privateendpointsprivatednszoneresourceids) | No | array | Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones. | +| [`roleAssignments`](#parameter-privateendpointsroleassignments) | No | array | 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'. | +| [`service`](#parameter-privateendpointsservice) | No | string | Optional. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob". | +| [`subnetResourceId`](#parameter-privateendpointssubnetresourceid) | Yes | string | Required. Resource ID of the subnet where the endpoint needs to be created. | +| [`tags`](#parameter-privateendpointstags) | No | object | Optional. Tags to be applied on all resources/resource groups in this deployment. | + +### Parameter: `privateEndpoints.applicationSecurityGroupResourceIds` + +Optional. Application security groups in which the private endpoint IP configuration is included. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.customDnsConfigs` + +Optional. Custom DNS configurations. + +- Required: No +- Type: array + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`fqdn`](#parameter-privateendpointscustomdnsconfigsfqdn) | No | string | | +| [`ipAddresses`](#parameter-privateendpointscustomdnsconfigsipaddresses) | Yes | array | | + +### Parameter: `privateEndpoints.customDnsConfigs.fqdn` +- Required: No +- Type: string + +### Parameter: `privateEndpoints.customDnsConfigs.ipAddresses` +- Required: Yes +- Type: array + + +### Parameter: `privateEndpoints.customNetworkInterfaceName` + +Optional. The custom name of the network interface attached to the private endpoint. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.enableTelemetry` + +Optional. Enable/Disable usage telemetry for module. + +- Required: No +- Type: bool + +### Parameter: `privateEndpoints.ipConfigurations` + +Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints. + +- Required: No +- Type: array + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`groupId`](#parameter-privateendpointsipconfigurationsgroupid) | Yes | string | | +| [`memberName`](#parameter-privateendpointsipconfigurationsmembername) | Yes | string | | +| [`name`](#parameter-privateendpointsipconfigurationsname) | Yes | string | | +| [`privateIpAddress`](#parameter-privateendpointsipconfigurationsprivateipaddress) | Yes | string | | + +### Parameter: `privateEndpoints.ipConfigurations.groupId` +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.ipConfigurations.memberName` +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.ipConfigurations.name` +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.ipConfigurations.privateIpAddress` +- Required: Yes +- Type: string + + +### Parameter: `privateEndpoints.location` + +Optional. The location to deploy the private endpoint to. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.lock` + +Optional. Specify the type of lock. + +- Required: No +- Type: object + +### Parameter: `privateEndpoints.manualPrivateLinkServiceConnections` + +Optional. Manual PrivateLink Service Connections. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.name` + +Optional. The name of the private endpoint. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.privateDnsZoneGroupName` + +Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.privateDnsZoneResourceIds` + +Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.roleAssignments` + +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'. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.service` + +Optional. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob". + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.subnetResourceId` + +Required. Resource ID of the subnet where the endpoint needs to be created. + +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.tags` + +Optional. Tags to be applied on all resources/resource groups in this deployment. + +- Required: No +- Type: object ### Parameter: `publicIpName` diff --git a/modules/databricks/workspace/main.bicep b/modules/databricks/workspace/main.bicep index 64c3bb3b4f..e59beaad47 100644 --- a/modules/databricks/workspace/main.bicep +++ b/modules/databricks/workspace/main.bicep @@ -121,7 +121,7 @@ param publicNetworkAccess string = 'Enabled' param requiredNsgRules string = 'AllRules' @description('Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible.') -param privateEndpoints array = [] +param privateEndpoints privateEndpointType @description('Optional. The name of logs that will be streamed. "allLogs" includes all possible logs for the resource. Set to \'\' to disable log collection.') @allowed([ @@ -335,27 +335,27 @@ resource workspace_roleAssignments 'Microsoft.Authorization/roleAssignments@2022 scope: workspace }] -module workspace_privateEndpoints '../../network/private-endpoint/main.bicep' = [for (privateEndpoint, index) in privateEndpoints: { - name: '${uniqueString(deployment().name, location)}-Databricks-PrivateEndpoint-${index}' +module workspace_privateEndpoints '../../network/private-endpoint/main.bicep' = [for (privateEndpoint, index) in (privateEndpoints ?? []): { + name: '${uniqueString(deployment().name, location)}-workspace-PrivateEndpoint-${index}' params: { groupIds: [ - privateEndpoint.service + privateEndpoint.?service ?? 'databricks_ui_api' ] - name: contains(privateEndpoint, 'name') ? privateEndpoint.name : 'pe-${last(split(workspace.id, '/'))}-${privateEndpoint.service}-${index}' + name: privateEndpoint.?name ?? 'pep-${last(split(workspace.id, '/'))}-${privateEndpoint.?service ?? 'databricks_ui_api'}-${index}' serviceResourceId: workspace.id subnetResourceId: privateEndpoint.subnetResourceId - enableDefaultTelemetry: enableReferencedModulesTelemetry - location: contains(privateEndpoint, 'location') ? privateEndpoint.location : reference(split(privateEndpoint.subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location + enableDefaultTelemetry: privateEndpoint.?enableDefaultTelemetry ?? enableReferencedModulesTelemetry + location: privateEndpoint.?location ?? reference(split(privateEndpoint.subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location lock: privateEndpoint.?lock ?? lock - privateDnsZoneGroupName: contains(privateEndpoint, 'privateDnsZoneGroupName') ? privateEndpoint.privateDnsZoneGroupName : 'default' - privateDnsZoneResourceIds: contains(privateEndpoint, 'privateDnsZoneResourceIds') ? privateEndpoint.privateDnsZoneResourceIds : [] - 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 : [] - applicationSecurityGroupResourceIds: contains(privateEndpoint, 'applicationSecurityGroupResourceIds') ? privateEndpoint.applicationSecurityGroupResourceIds : [] - customNetworkInterfaceName: contains(privateEndpoint, 'customNetworkInterfaceName') ? privateEndpoint.customNetworkInterfaceName : '' + privateDnsZoneGroupName: privateEndpoint.?privateDnsZoneGroupName + privateDnsZoneResourceIds: privateEndpoint.?privateDnsZoneResourceIds + roleAssignments: privateEndpoint.?roleAssignments + tags: privateEndpoint.?tags ?? tags + manualPrivateLinkServiceConnections: privateEndpoint.?manualPrivateLinkServiceConnections + customDnsConfigs: privateEndpoint.?customDnsConfigs + ipConfigurations: privateEndpoint.?ipConfigurations + applicationSecurityGroupResourceIds: privateEndpoint.?applicationSecurityGroupResourceIds + customNetworkInterfaceName: privateEndpoint.?customNetworkInterfaceName } }] @@ -405,3 +405,58 @@ type roleAssignmentType = { @description('Optional. The Resource Id of the delegated managed identity resource.') delegatedManagedIdentityResourceId: string? }[]? + +type privateEndpointType = { + @description('Optional. The name of the private endpoint.') + name: string? + + @description('Optional. The location to deploy the private endpoint to.') + location: string? + + @description('Optional. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob".') + service: string? + + @description('Required. Resource ID of the subnet where the endpoint needs to be created.') + subnetResourceId: string + + @description('Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided.') + privateDnsZoneGroupName: string? + + @description('Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones.') + privateDnsZoneResourceIds: string[]? + + @description('Optional. Custom DNS configurations.') + customDnsConfigs: { + fqdn: string? + ipAddresses: string[] + }[]? + + @description('Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints.') + ipConfigurations: { + name: string + groupId: string + memberName: string + privateIpAddress: string + }[]? + + @description('Optional. Application security groups in which the private endpoint IP configuration is included.') + applicationSecurityGroupResourceIds: string[]? + + @description('Optional. The custom name of the network interface attached to the private endpoint.') + customNetworkInterfaceName: string? + + @description('Optional. Specify the type of lock.') + lock: lockType + + @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\'.') + roleAssignments: roleAssignmentType + + @description('Optional. Tags to be applied on all resources/resource groups in this deployment.') + tags: object? + + @description('Optional. Manual PrivateLink Service Connections.') + manualPrivateLinkServiceConnections: array? + + @description('Optional. Enable/Disable usage telemetry for module.') + enableTelemetry: bool? +}[]? diff --git a/modules/databricks/workspace/main.json b/modules/databricks/workspace/main.json index e52357cddd..33d22fb540 100644 --- a/modules/databricks/workspace/main.json +++ b/modules/databricks/workspace/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.22.6.54827", - "templateHash": "1159355257291506829" + "templateHash": "13976222918175315424" }, "name": "Azure Databricks Workspaces", "description": "This module deploys an Azure Databricks Workspace.", @@ -103,6 +103,155 @@ } }, "nullable": true + }, + "privateEndpointType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private endpoint." + } + }, + "location": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The location to deploy the private endpoint to." + } + }, + "service": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The service (sub-) type to deploy the private endpoint for. For example \"vault\" or \"blob\"." + } + }, + "subnetResourceId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the subnet where the endpoint needs to be created." + } + }, + "privateDnsZoneGroupName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided." + } + }, + "privateDnsZoneResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones." + } + }, + "customDnsConfigs": { + "type": "array", + "items": { + "type": "object", + "properties": { + "fqdn": { + "type": "string", + "nullable": true + }, + "ipAddresses": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. Custom DNS configurations." + } + }, + "ipConfigurations": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "groupId": { + "type": "string" + }, + "memberName": { + "type": "string" + }, + "privateIpAddress": { + "type": "string" + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints." + } + }, + "applicationSecurityGroupResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. Application security groups in which the private endpoint IP configuration is included." + } + }, + "customNetworkInterfaceName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The custom name of the network interface attached to the private endpoint." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "metadata": { + "description": "Optional. Specify the type of lock." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "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'." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags to be applied on all resources/resource groups in this deployment." + } + }, + "manualPrivateLinkServiceConnections": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. Manual PrivateLink Service Connections." + } + }, + "enableTelemetry": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + } + } + }, + "nullable": true } }, "parameters": { @@ -362,8 +511,7 @@ } }, "privateEndpoints": { - "type": "array", - "defaultValue": [], + "$ref": "#/definitions/privateEndpointType", "metadata": { "description": "Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible." } @@ -554,11 +702,11 @@ "workspace_privateEndpoints": { "copy": { "name": "workspace_privateEndpoints", - "count": "[length(parameters('privateEndpoints'))]" + "count": "[length(coalesce(parameters('privateEndpoints'), createArray()))]" }, "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", - "name": "[format('{0}-Databricks-PrivateEndpoint-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "name": "[format('{0}-workspace-PrivateEndpoint-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", "properties": { "expressionEvaluationOptions": { "scope": "inner" @@ -567,32 +715,54 @@ "parameters": { "groupIds": { "value": [ - "[parameters('privateEndpoints')[copyIndex()].service]" + "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'databricks_ui_api')]" ] }, - "name": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'name'), createObject('value', parameters('privateEndpoints')[copyIndex()].name), createObject('value', format('pe-{0}-{1}-{2}', last(split(resourceId('Microsoft.Databricks/workspaces', parameters('name')), '/')), parameters('privateEndpoints')[copyIndex()].service, copyIndex())))]", + "name": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'name'), format('pep-{0}-{1}-{2}', last(split(resourceId('Microsoft.Databricks/workspaces', parameters('name')), '/')), coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'databricks_ui_api'), copyIndex()))]" + }, "serviceResourceId": { "value": "[resourceId('Microsoft.Databricks/workspaces', parameters('name'))]" }, "subnetResourceId": { - "value": "[parameters('privateEndpoints')[copyIndex()].subnetResourceId]" + "value": "[coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId]" }, "enableDefaultTelemetry": { - "value": "[variables('enableReferencedModulesTelemetry')]" + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'enableDefaultTelemetry'), variables('enableReferencedModulesTelemetry'))]" + }, + "location": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'location'), reference(split(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location)]" }, - "location": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'location'), createObject('value', parameters('privateEndpoints')[copyIndex()].location), createObject('value', reference(split(parameters('privateEndpoints')[copyIndex()].subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location))]", "lock": { - "value": "[coalesce(tryGet(parameters('privateEndpoints')[copyIndex()], 'lock'), parameters('lock'))]" + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'lock'), parameters('lock'))]" + }, + "privateDnsZoneGroupName": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneGroupName')]" + }, + "privateDnsZoneResourceIds": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneResourceIds')]" + }, + "roleAssignments": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'roleAssignments')]" }, - "privateDnsZoneGroupName": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'privateDnsZoneGroupName'), createObject('value', parameters('privateEndpoints')[copyIndex()].privateDnsZoneGroupName), createObject('value', 'default'))]", - "privateDnsZoneResourceIds": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'privateDnsZoneResourceIds'), createObject('value', parameters('privateEndpoints')[copyIndex()].privateDnsZoneResourceIds), createObject('value', createArray()))]", - "roleAssignments": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'roleAssignments'), createObject('value', parameters('privateEndpoints')[copyIndex()].roleAssignments), createObject('value', createArray()))]", - "tags": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'tags'), createObject('value', parameters('privateEndpoints')[copyIndex()].tags), createObject('value', createObject()))]", - "manualPrivateLinkServiceConnections": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'manualPrivateLinkServiceConnections'), createObject('value', parameters('privateEndpoints')[copyIndex()].manualPrivateLinkServiceConnections), createObject('value', createArray()))]", - "customDnsConfigs": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'customDnsConfigs'), createObject('value', parameters('privateEndpoints')[copyIndex()].customDnsConfigs), createObject('value', createArray()))]", - "ipConfigurations": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'ipConfigurations'), createObject('value', parameters('privateEndpoints')[copyIndex()].ipConfigurations), createObject('value', createArray()))]", - "applicationSecurityGroupResourceIds": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'applicationSecurityGroupResourceIds'), createObject('value', parameters('privateEndpoints')[copyIndex()].applicationSecurityGroupResourceIds), createObject('value', createArray()))]", - "customNetworkInterfaceName": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'customNetworkInterfaceName'), createObject('value', parameters('privateEndpoints')[copyIndex()].customNetworkInterfaceName), createObject('value', ''))]" + "tags": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'tags'), parameters('tags'))]" + }, + "manualPrivateLinkServiceConnections": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'manualPrivateLinkServiceConnections')]" + }, + "customDnsConfigs": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customDnsConfigs')]" + }, + "ipConfigurations": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'ipConfigurations')]" + }, + "applicationSecurityGroupResourceIds": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'applicationSecurityGroupResourceIds')]" + }, + "customNetworkInterfaceName": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customNetworkInterfaceName')]" + } }, "template": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", diff --git a/modules/digital-twins/digital-twins-instance/.test/common/main.test.bicep b/modules/digital-twins/digital-twins-instance/.test/common/main.test.bicep index e28f648c50..749dbf29fd 100644 --- a/modules/digital-twins/digital-twins-instance/.test/common/main.test.bicep +++ b/modules/digital-twins/digital-twins-instance/.test/common/main.test.bicep @@ -101,11 +101,8 @@ module testDeployment '../../main.bicep' = { privateEndpoints: [ { privateDnsZoneResourceIds: [ - nestedDependencies.outputs.privateDNSZoneResourceId - ] - service: 'API' subnetResourceId: nestedDependencies.outputs.subnetResourceId } ] diff --git a/modules/digital-twins/digital-twins-instance/README.md b/modules/digital-twins/digital-twins-instance/README.md index 072c8dcdb8..e3a5585230 100644 --- a/modules/digital-twins/digital-twins-instance/README.md +++ b/modules/digital-twins/digital-twins-instance/README.md @@ -73,7 +73,6 @@ module digitalTwinsInstance 'br:bicep/modules/digital-twins.digital-twins-instan privateDnsZoneResourceIds: [ '' ] - service: 'API' subnetResourceId: '' } ] @@ -160,7 +159,6 @@ module digitalTwinsInstance 'br:bicep/modules/digital-twins.digital-twins-instan "privateDnsZoneResourceIds": [ "" ], - "service": "API", "subnetResourceId": "" } ] @@ -399,7 +397,168 @@ The name of the Digital Twin Instance. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible. - Required: No - Type: array -- Default: `[]` + + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`applicationSecurityGroupResourceIds`](#parameter-privateendpointsapplicationsecuritygroupresourceids) | No | array | Optional. Application security groups in which the private endpoint IP configuration is included. | +| [`customDnsConfigs`](#parameter-privateendpointscustomdnsconfigs) | No | array | Optional. Custom DNS configurations. | +| [`customNetworkInterfaceName`](#parameter-privateendpointscustomnetworkinterfacename) | No | string | Optional. The custom name of the network interface attached to the private endpoint. | +| [`enableTelemetry`](#parameter-privateendpointsenabletelemetry) | No | bool | Optional. Enable/Disable usage telemetry for module. | +| [`ipConfigurations`](#parameter-privateendpointsipconfigurations) | No | array | Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints. | +| [`location`](#parameter-privateendpointslocation) | No | string | Optional. The location to deploy the private endpoint to. | +| [`lock`](#parameter-privateendpointslock) | No | object | Optional. Specify the type of lock. | +| [`manualPrivateLinkServiceConnections`](#parameter-privateendpointsmanualprivatelinkserviceconnections) | No | array | Optional. Manual PrivateLink Service Connections. | +| [`name`](#parameter-privateendpointsname) | No | string | Optional. The name of the private endpoint. | +| [`privateDnsZoneGroupName`](#parameter-privateendpointsprivatednszonegroupname) | No | string | Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided. | +| [`privateDnsZoneResourceIds`](#parameter-privateendpointsprivatednszoneresourceids) | No | array | Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones. | +| [`roleAssignments`](#parameter-privateendpointsroleassignments) | No | array | 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'. | +| [`service`](#parameter-privateendpointsservice) | No | string | Optional. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob". | +| [`subnetResourceId`](#parameter-privateendpointssubnetresourceid) | Yes | string | Required. Resource ID of the subnet where the endpoint needs to be created. | +| [`tags`](#parameter-privateendpointstags) | No | object | Optional. Tags to be applied on all resources/resource groups in this deployment. | + +### Parameter: `privateEndpoints.applicationSecurityGroupResourceIds` + +Optional. Application security groups in which the private endpoint IP configuration is included. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.customDnsConfigs` + +Optional. Custom DNS configurations. + +- Required: No +- Type: array + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`fqdn`](#parameter-privateendpointscustomdnsconfigsfqdn) | No | string | | +| [`ipAddresses`](#parameter-privateendpointscustomdnsconfigsipaddresses) | Yes | array | | + +### Parameter: `privateEndpoints.customDnsConfigs.fqdn` +- Required: No +- Type: string + +### Parameter: `privateEndpoints.customDnsConfigs.ipAddresses` +- Required: Yes +- Type: array + + +### Parameter: `privateEndpoints.customNetworkInterfaceName` + +Optional. The custom name of the network interface attached to the private endpoint. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.enableTelemetry` + +Optional. Enable/Disable usage telemetry for module. + +- Required: No +- Type: bool + +### Parameter: `privateEndpoints.ipConfigurations` + +Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints. + +- Required: No +- Type: array + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`groupId`](#parameter-privateendpointsipconfigurationsgroupid) | Yes | string | | +| [`memberName`](#parameter-privateendpointsipconfigurationsmembername) | Yes | string | | +| [`name`](#parameter-privateendpointsipconfigurationsname) | Yes | string | | +| [`privateIpAddress`](#parameter-privateendpointsipconfigurationsprivateipaddress) | Yes | string | | + +### Parameter: `privateEndpoints.ipConfigurations.groupId` +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.ipConfigurations.memberName` +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.ipConfigurations.name` +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.ipConfigurations.privateIpAddress` +- Required: Yes +- Type: string + + +### Parameter: `privateEndpoints.location` + +Optional. The location to deploy the private endpoint to. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.lock` + +Optional. Specify the type of lock. + +- Required: No +- Type: object + +### Parameter: `privateEndpoints.manualPrivateLinkServiceConnections` + +Optional. Manual PrivateLink Service Connections. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.name` + +Optional. The name of the private endpoint. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.privateDnsZoneGroupName` + +Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.privateDnsZoneResourceIds` + +Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.roleAssignments` + +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'. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.service` + +Optional. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob". + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.subnetResourceId` + +Required. Resource ID of the subnet where the endpoint needs to be created. + +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.tags` + +Optional. Tags to be applied on all resources/resource groups in this deployment. + +- Required: No +- Type: object ### Parameter: `publicNetworkAccess` diff --git a/modules/digital-twins/digital-twins-instance/main.bicep b/modules/digital-twins/digital-twins-instance/main.bicep index afb4470480..50675134ab 100644 --- a/modules/digital-twins/digital-twins-instance/main.bicep +++ b/modules/digital-twins/digital-twins-instance/main.bicep @@ -32,7 +32,7 @@ param eventGridEndpoint object = {} param serviceBusEndpoint object = {} @description('Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible.') -param privateEndpoints array = [] +param privateEndpoints privateEndpointType @description('Optional. Whether or not public network access is allowed for this resource. For security reasons it should be disabled. If not specified, it will be disabled by default if private endpoints are set.') @allowed([ @@ -194,24 +194,27 @@ module digitalTwinsInstance_serviceBusEndpoint 'endpoint--service-bus/main.bicep } } -module digitalTwinsInstance_privateEndpoints '../../network/private-endpoint/main.bicep' = [for (privateEndpoint, index) in privateEndpoints: { +module digitalTwinsInstance_privateEndpoints '../../network/private-endpoint/main.bicep' = [for (privateEndpoint, index) in (privateEndpoints ?? []): { name: '${uniqueString(deployment().name, location)}-digitalTwinsInstance-PrivateEndpoint-${index}' params: { groupIds: [ - privateEndpoint.service + privateEndpoint.?service ?? 'API' ] - name: contains(privateEndpoint, 'name') ? privateEndpoint.name : 'pe-${last(split(digitalTwinsInstance.id, '/'))}-${privateEndpoint.service}-${index}' + name: privateEndpoint.?name ?? 'pep-${last(split(digitalTwinsInstance.id, '/'))}-${privateEndpoint.?service ?? 'API'}-${index}' serviceResourceId: digitalTwinsInstance.id subnetResourceId: privateEndpoint.subnetResourceId - enableDefaultTelemetry: enableReferencedModulesTelemetry - location: contains(privateEndpoint, 'location') ? privateEndpoint.location : reference(split(privateEndpoint.subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location + enableDefaultTelemetry: privateEndpoint.?enableDefaultTelemetry ?? enableReferencedModulesTelemetry + location: privateEndpoint.?location ?? reference(split(privateEndpoint.subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location lock: privateEndpoint.?lock ?? lock - privateDnsZoneGroupName: contains(privateEndpoint, 'privateDnsZoneGroupName') ? privateEndpoint.privateDnsZoneGroupName : 'default' - privateDnsZoneResourceIds: contains(privateEndpoint, 'privateDnsZoneResourceIds') ? privateEndpoint.privateDnsZoneResourceIds : [] - roleAssignments: contains(privateEndpoint, 'roleAssignments') ? privateEndpoint.roleAssignments : [] - tags: contains(privateEndpoint, 'tags') ? privateEndpoint.tags : {} - manualPrivateLinkServiceConnections: contains(privateEndpoint, 'manualPrivateLinkServiceConnections') ? privateEndpoint.manualPrivateLinkServiceConnections : [] - customDnsConfigs: contains(privateEndpoint, 'customDnsConfigs') ? privateEndpoint.customDnsConfigs : [] + privateDnsZoneGroupName: privateEndpoint.?privateDnsZoneGroupName + privateDnsZoneResourceIds: privateEndpoint.?privateDnsZoneResourceIds + roleAssignments: privateEndpoint.?roleAssignments + tags: privateEndpoint.?tags ?? tags + manualPrivateLinkServiceConnections: privateEndpoint.?manualPrivateLinkServiceConnections + customDnsConfigs: privateEndpoint.?customDnsConfigs + ipConfigurations: privateEndpoint.?ipConfigurations + applicationSecurityGroupResourceIds: privateEndpoint.?applicationSecurityGroupResourceIds + customNetworkInterfaceName: privateEndpoint.?customNetworkInterfaceName } }] @@ -300,3 +303,58 @@ type roleAssignmentType = { @description('Optional. The Resource Id of the delegated managed identity resource.') delegatedManagedIdentityResourceId: string? }[]? + +type privateEndpointType = { + @description('Optional. The name of the private endpoint.') + name: string? + + @description('Optional. The location to deploy the private endpoint to.') + location: string? + + @description('Optional. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob".') + service: string? + + @description('Required. Resource ID of the subnet where the endpoint needs to be created.') + subnetResourceId: string + + @description('Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided.') + privateDnsZoneGroupName: string? + + @description('Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones.') + privateDnsZoneResourceIds: string[]? + + @description('Optional. Custom DNS configurations.') + customDnsConfigs: { + fqdn: string? + ipAddresses: string[] + }[]? + + @description('Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints.') + ipConfigurations: { + name: string + groupId: string + memberName: string + privateIpAddress: string + }[]? + + @description('Optional. Application security groups in which the private endpoint IP configuration is included.') + applicationSecurityGroupResourceIds: string[]? + + @description('Optional. The custom name of the network interface attached to the private endpoint.') + customNetworkInterfaceName: string? + + @description('Optional. Specify the type of lock.') + lock: lockType + + @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\'.') + roleAssignments: roleAssignmentType + + @description('Optional. Tags to be applied on all resources/resource groups in this deployment.') + tags: object? + + @description('Optional. Manual PrivateLink Service Connections.') + manualPrivateLinkServiceConnections: array? + + @description('Optional. Enable/Disable usage telemetry for module.') + enableTelemetry: bool? +}[]? diff --git a/modules/digital-twins/digital-twins-instance/main.json b/modules/digital-twins/digital-twins-instance/main.json index 6c8da212c8..482ce8f162 100644 --- a/modules/digital-twins/digital-twins-instance/main.json +++ b/modules/digital-twins/digital-twins-instance/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.22.6.54827", - "templateHash": "3171798738610144721" + "templateHash": "9608211624900685479" }, "name": "Digital Twins Instances", "description": "This module deploys an Azure Digital Twins Instance.", @@ -103,6 +103,155 @@ } }, "nullable": true + }, + "privateEndpointType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private endpoint." + } + }, + "location": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The location to deploy the private endpoint to." + } + }, + "service": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The service (sub-) type to deploy the private endpoint for. For example \"vault\" or \"blob\"." + } + }, + "subnetResourceId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the subnet where the endpoint needs to be created." + } + }, + "privateDnsZoneGroupName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided." + } + }, + "privateDnsZoneResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones." + } + }, + "customDnsConfigs": { + "type": "array", + "items": { + "type": "object", + "properties": { + "fqdn": { + "type": "string", + "nullable": true + }, + "ipAddresses": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. Custom DNS configurations." + } + }, + "ipConfigurations": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "groupId": { + "type": "string" + }, + "memberName": { + "type": "string" + }, + "privateIpAddress": { + "type": "string" + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints." + } + }, + "applicationSecurityGroupResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. Application security groups in which the private endpoint IP configuration is included." + } + }, + "customNetworkInterfaceName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The custom name of the network interface attached to the private endpoint." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "metadata": { + "description": "Optional. Specify the type of lock." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "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'." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags to be applied on all resources/resource groups in this deployment." + } + }, + "manualPrivateLinkServiceConnections": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. Manual PrivateLink Service Connections." + } + }, + "enableTelemetry": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + } + } + }, + "nullable": true } }, "parameters": { @@ -170,8 +319,7 @@ } }, "privateEndpoints": { - "type": "array", - "defaultValue": [], + "$ref": "#/definitions/privateEndpointType", "metadata": { "description": "Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible." } @@ -918,7 +1066,7 @@ "digitalTwinsInstance_privateEndpoints": { "copy": { "name": "digitalTwinsInstance_privateEndpoints", - "count": "[length(parameters('privateEndpoints'))]" + "count": "[length(coalesce(parameters('privateEndpoints'), createArray()))]" }, "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", @@ -931,29 +1079,54 @@ "parameters": { "groupIds": { "value": [ - "[parameters('privateEndpoints')[copyIndex()].service]" + "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'API')]" ] }, - "name": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'name'), createObject('value', parameters('privateEndpoints')[copyIndex()].name), createObject('value', format('pe-{0}-{1}-{2}', last(split(resourceId('Microsoft.DigitalTwins/digitalTwinsInstances', parameters('name')), '/')), parameters('privateEndpoints')[copyIndex()].service, copyIndex())))]", + "name": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'name'), format('pep-{0}-{1}-{2}', last(split(resourceId('Microsoft.DigitalTwins/digitalTwinsInstances', parameters('name')), '/')), coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'API'), copyIndex()))]" + }, "serviceResourceId": { "value": "[resourceId('Microsoft.DigitalTwins/digitalTwinsInstances', parameters('name'))]" }, "subnetResourceId": { - "value": "[parameters('privateEndpoints')[copyIndex()].subnetResourceId]" + "value": "[coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId]" }, "enableDefaultTelemetry": { - "value": "[variables('enableReferencedModulesTelemetry')]" + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'enableDefaultTelemetry'), variables('enableReferencedModulesTelemetry'))]" + }, + "location": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'location'), reference(split(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location)]" }, - "location": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'location'), createObject('value', parameters('privateEndpoints')[copyIndex()].location), createObject('value', reference(split(parameters('privateEndpoints')[copyIndex()].subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location))]", "lock": { - "value": "[coalesce(tryGet(parameters('privateEndpoints')[copyIndex()], 'lock'), parameters('lock'))]" + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'lock'), parameters('lock'))]" + }, + "privateDnsZoneGroupName": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneGroupName')]" + }, + "privateDnsZoneResourceIds": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneResourceIds')]" + }, + "roleAssignments": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'roleAssignments')]" }, - "privateDnsZoneGroupName": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'privateDnsZoneGroupName'), createObject('value', parameters('privateEndpoints')[copyIndex()].privateDnsZoneGroupName), createObject('value', 'default'))]", - "privateDnsZoneResourceIds": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'privateDnsZoneResourceIds'), createObject('value', parameters('privateEndpoints')[copyIndex()].privateDnsZoneResourceIds), createObject('value', createArray()))]", - "roleAssignments": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'roleAssignments'), createObject('value', parameters('privateEndpoints')[copyIndex()].roleAssignments), createObject('value', createArray()))]", - "tags": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'tags'), createObject('value', parameters('privateEndpoints')[copyIndex()].tags), createObject('value', createObject()))]", - "manualPrivateLinkServiceConnections": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'manualPrivateLinkServiceConnections'), createObject('value', parameters('privateEndpoints')[copyIndex()].manualPrivateLinkServiceConnections), createObject('value', createArray()))]", - "customDnsConfigs": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'customDnsConfigs'), createObject('value', parameters('privateEndpoints')[copyIndex()].customDnsConfigs), createObject('value', createArray()))]" + "tags": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'tags'), parameters('tags'))]" + }, + "manualPrivateLinkServiceConnections": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'manualPrivateLinkServiceConnections')]" + }, + "customDnsConfigs": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customDnsConfigs')]" + }, + "ipConfigurations": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'ipConfigurations')]" + }, + "applicationSecurityGroupResourceIds": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'applicationSecurityGroupResourceIds')]" + }, + "customNetworkInterfaceName": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customNetworkInterfaceName')]" + } }, "template": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", diff --git a/modules/document-db/database-account/.test/sqldb/main.test.bicep b/modules/document-db/database-account/.test/sqldb/main.test.bicep index 5d54bf3d10..7188f6732d 100644 --- a/modules/document-db/database-account/.test/sqldb/main.test.bicep +++ b/modules/document-db/database-account/.test/sqldb/main.test.bicep @@ -85,9 +85,7 @@ module testDeployment '../../main.bicep' = { privateEndpoints: [ { privateDnsZoneResourceIds: [ - nestedDependencies.outputs.privateDNSZoneResourceId - ] service: 'Sql' subnetResourceId: nestedDependencies.outputs.subnetResourceId diff --git a/modules/document-db/database-account/README.md b/modules/document-db/database-account/README.md index 5f94a99e44..6ab97ff79f 100644 --- a/modules/document-db/database-account/README.md +++ b/modules/document-db/database-account/README.md @@ -1457,7 +1457,168 @@ Name of the Database Account. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible. - Required: No - Type: array -- Default: `[]` + + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`applicationSecurityGroupResourceIds`](#parameter-privateendpointsapplicationsecuritygroupresourceids) | No | array | Optional. Application security groups in which the private endpoint IP configuration is included. | +| [`customDnsConfigs`](#parameter-privateendpointscustomdnsconfigs) | No | array | Optional. Custom DNS configurations. | +| [`customNetworkInterfaceName`](#parameter-privateendpointscustomnetworkinterfacename) | No | string | Optional. The custom name of the network interface attached to the private endpoint. | +| [`enableTelemetry`](#parameter-privateendpointsenabletelemetry) | No | bool | Optional. Enable/Disable usage telemetry for module. | +| [`ipConfigurations`](#parameter-privateendpointsipconfigurations) | No | array | Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints. | +| [`location`](#parameter-privateendpointslocation) | No | string | Optional. The location to deploy the private endpoint to. | +| [`lock`](#parameter-privateendpointslock) | No | object | Optional. Specify the type of lock. | +| [`manualPrivateLinkServiceConnections`](#parameter-privateendpointsmanualprivatelinkserviceconnections) | No | array | Optional. Manual PrivateLink Service Connections. | +| [`name`](#parameter-privateendpointsname) | No | string | Optional. The name of the private endpoint. | +| [`privateDnsZoneGroupName`](#parameter-privateendpointsprivatednszonegroupname) | No | string | Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided. | +| [`privateDnsZoneResourceIds`](#parameter-privateendpointsprivatednszoneresourceids) | No | array | Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones. | +| [`roleAssignments`](#parameter-privateendpointsroleassignments) | No | array | 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'. | +| [`service`](#parameter-privateendpointsservice) | Yes | string | Required. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob". | +| [`subnetResourceId`](#parameter-privateendpointssubnetresourceid) | Yes | string | Required. Resource ID of the subnet where the endpoint needs to be created. | +| [`tags`](#parameter-privateendpointstags) | No | object | Optional. Tags to be applied on all resources/resource groups in this deployment. | + +### Parameter: `privateEndpoints.applicationSecurityGroupResourceIds` + +Optional. Application security groups in which the private endpoint IP configuration is included. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.customDnsConfigs` + +Optional. Custom DNS configurations. + +- Required: No +- Type: array + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`fqdn`](#parameter-privateendpointscustomdnsconfigsfqdn) | No | string | | +| [`ipAddresses`](#parameter-privateendpointscustomdnsconfigsipaddresses) | Yes | array | | + +### Parameter: `privateEndpoints.customDnsConfigs.fqdn` +- Required: No +- Type: string + +### Parameter: `privateEndpoints.customDnsConfigs.ipAddresses` +- Required: Yes +- Type: array + + +### Parameter: `privateEndpoints.customNetworkInterfaceName` + +Optional. The custom name of the network interface attached to the private endpoint. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.enableTelemetry` + +Optional. Enable/Disable usage telemetry for module. + +- Required: No +- Type: bool + +### Parameter: `privateEndpoints.ipConfigurations` + +Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints. + +- Required: No +- Type: array + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`groupId`](#parameter-privateendpointsipconfigurationsgroupid) | Yes | string | | +| [`memberName`](#parameter-privateendpointsipconfigurationsmembername) | Yes | string | | +| [`name`](#parameter-privateendpointsipconfigurationsname) | Yes | string | | +| [`privateIpAddress`](#parameter-privateendpointsipconfigurationsprivateipaddress) | Yes | string | | + +### Parameter: `privateEndpoints.ipConfigurations.groupId` +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.ipConfigurations.memberName` +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.ipConfigurations.name` +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.ipConfigurations.privateIpAddress` +- Required: Yes +- Type: string + + +### Parameter: `privateEndpoints.location` + +Optional. The location to deploy the private endpoint to. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.lock` + +Optional. Specify the type of lock. + +- Required: No +- Type: object + +### Parameter: `privateEndpoints.manualPrivateLinkServiceConnections` + +Optional. Manual PrivateLink Service Connections. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.name` + +Optional. The name of the private endpoint. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.privateDnsZoneGroupName` + +Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.privateDnsZoneResourceIds` + +Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.roleAssignments` + +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'. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.service` + +Required. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob". + +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.subnetResourceId` + +Required. Resource ID of the subnet where the endpoint needs to be created. + +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.tags` + +Optional. Tags to be applied on all resources/resource groups in this deployment. + +- Required: No +- Type: object ### Parameter: `roleAssignments` diff --git a/modules/document-db/database-account/main.bicep b/modules/document-db/database-account/main.bicep index 5f69ff9d06..6cb9f5b506 100644 --- a/modules/document-db/database-account/main.bicep +++ b/modules/document-db/database-account/main.bicep @@ -164,7 +164,7 @@ param backupRetentionIntervalInHours int = 8 param backupStorageRedundancy string = 'Local' @description('Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible.') -param privateEndpoints array = [] +param privateEndpoints privateEndpointType var diagnosticsLogsSpecified = [for category in filter(diagnosticLogCategoriesToEnable, item => item != 'allLogs' && item != ''): { category: category @@ -360,27 +360,27 @@ module databaseAccount_gremlinDatabases 'gremlin-database/main.bicep' = [for gre } }] -module databaseAccount_privateEndpoints '../../network/private-endpoint/main.bicep' = [for (privateEndpoint, index) in privateEndpoints: { - name: '${uniqueString(deployment().name, location)}-CosmosDB-PrivateEndpoint-${index}' +module databaseAccount_privateEndpoints '../../network/private-endpoint/main.bicep' = [for (privateEndpoint, index) in (privateEndpoints ?? []): { + name: '${uniqueString(deployment().name, location)}-databaseAccount-PrivateEndpoint-${index}' params: { groupIds: [ privateEndpoint.service ] - name: contains(privateEndpoint, 'name') ? privateEndpoint.name : 'pe-${last(split(databaseAccount.id, '/'))}-${privateEndpoint.service}-${index}' + name: privateEndpoint.?name ?? 'pep-${last(split(databaseAccount.id, '/'))}-${privateEndpoint.?service ?? privateEndpoint.service}-${index}' serviceResourceId: databaseAccount.id subnetResourceId: privateEndpoint.subnetResourceId - enableDefaultTelemetry: enableReferencedModulesTelemetry - location: contains(privateEndpoint, 'location') ? privateEndpoint.location : reference(split(privateEndpoint.subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location + enableDefaultTelemetry: privateEndpoint.?enableDefaultTelemetry ?? enableReferencedModulesTelemetry + location: privateEndpoint.?location ?? reference(split(privateEndpoint.subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location lock: privateEndpoint.?lock ?? lock - privateDnsZoneGroupName: contains(privateEndpoint, 'privateDnsZoneGroupName') ? privateEndpoint.privateDnsZoneGroupName : 'default' - privateDnsZoneResourceIds: contains(privateEndpoint, 'privateDnsZoneResourceIds') ? privateEndpoint.privateDnsZoneResourceIds : [] - 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 : [] - applicationSecurityGroupResourceIds: contains(privateEndpoint, 'applicationSecurityGroupResourceIds') ? privateEndpoint.applicationSecurityGroupResourceIds : [] - customNetworkInterfaceName: contains(privateEndpoint, 'customNetworkInterfaceName') ? privateEndpoint.customNetworkInterfaceName : '' + privateDnsZoneGroupName: privateEndpoint.?privateDnsZoneGroupName + privateDnsZoneResourceIds: privateEndpoint.?privateDnsZoneResourceIds + roleAssignments: privateEndpoint.?roleAssignments + tags: privateEndpoint.?tags ?? tags + manualPrivateLinkServiceConnections: privateEndpoint.?manualPrivateLinkServiceConnections + customDnsConfigs: privateEndpoint.?customDnsConfigs + ipConfigurations: privateEndpoint.?ipConfigurations + applicationSecurityGroupResourceIds: privateEndpoint.?applicationSecurityGroupResourceIds + customNetworkInterfaceName: privateEndpoint.?customNetworkInterfaceName } }] @@ -433,3 +433,58 @@ type roleAssignmentType = { @description('Optional. The Resource Id of the delegated managed identity resource.') delegatedManagedIdentityResourceId: string? }[]? + +type privateEndpointType = { + @description('Optional. The name of the private endpoint.') + name: string? + + @description('Optional. The location to deploy the private endpoint to.') + location: string? + + @description('Required. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob".') + service: string + + @description('Required. Resource ID of the subnet where the endpoint needs to be created.') + subnetResourceId: string + + @description('Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided.') + privateDnsZoneGroupName: string? + + @description('Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones.') + privateDnsZoneResourceIds: string[]? + + @description('Optional. Custom DNS configurations.') + customDnsConfigs: { + fqdn: string? + ipAddresses: string[] + }[]? + + @description('Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints.') + ipConfigurations: { + name: string + groupId: string + memberName: string + privateIpAddress: string + }[]? + + @description('Optional. Application security groups in which the private endpoint IP configuration is included.') + applicationSecurityGroupResourceIds: string[]? + + @description('Optional. The custom name of the network interface attached to the private endpoint.') + customNetworkInterfaceName: string? + + @description('Optional. Specify the type of lock.') + lock: lockType + + @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\'.') + roleAssignments: roleAssignmentType + + @description('Optional. Tags to be applied on all resources/resource groups in this deployment.') + tags: object? + + @description('Optional. Manual PrivateLink Service Connections.') + manualPrivateLinkServiceConnections: array? + + @description('Optional. Enable/Disable usage telemetry for module.') + enableTelemetry: bool? +}[]? diff --git a/modules/document-db/database-account/main.json b/modules/document-db/database-account/main.json index 363ae739f7..498d4cc162 100644 --- a/modules/document-db/database-account/main.json +++ b/modules/document-db/database-account/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.22.6.54827", - "templateHash": "10157225997571423198" + "templateHash": "15078236941078357698" }, "name": "DocumentDB Database Accounts", "description": "This module deploys a DocumentDB Database Account.", @@ -103,6 +103,154 @@ } }, "nullable": true + }, + "privateEndpointType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private endpoint." + } + }, + "location": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The location to deploy the private endpoint to." + } + }, + "service": { + "type": "string", + "metadata": { + "description": "Required. The service (sub-) type to deploy the private endpoint for. For example \"vault\" or \"blob\"." + } + }, + "subnetResourceId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the subnet where the endpoint needs to be created." + } + }, + "privateDnsZoneGroupName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided." + } + }, + "privateDnsZoneResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones." + } + }, + "customDnsConfigs": { + "type": "array", + "items": { + "type": "object", + "properties": { + "fqdn": { + "type": "string", + "nullable": true + }, + "ipAddresses": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. Custom DNS configurations." + } + }, + "ipConfigurations": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "groupId": { + "type": "string" + }, + "memberName": { + "type": "string" + }, + "privateIpAddress": { + "type": "string" + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints." + } + }, + "applicationSecurityGroupResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. Application security groups in which the private endpoint IP configuration is included." + } + }, + "customNetworkInterfaceName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The custom name of the network interface attached to the private endpoint." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "metadata": { + "description": "Optional. Specify the type of lock." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "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'." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags to be applied on all resources/resource groups in this deployment." + } + }, + "manualPrivateLinkServiceConnections": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. Manual PrivateLink Service Connections." + } + }, + "enableTelemetry": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + } + } + }, + "nullable": true } }, "parameters": { @@ -392,8 +540,7 @@ } }, "privateEndpoints": { - "type": "array", - "defaultValue": [], + "$ref": "#/definitions/privateEndpointType", "metadata": { "description": "Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible." } @@ -1528,11 +1675,11 @@ "databaseAccount_privateEndpoints": { "copy": { "name": "databaseAccount_privateEndpoints", - "count": "[length(parameters('privateEndpoints'))]" + "count": "[length(coalesce(parameters('privateEndpoints'), createArray()))]" }, "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", - "name": "[format('{0}-CosmosDB-PrivateEndpoint-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "name": "[format('{0}-databaseAccount-PrivateEndpoint-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", "properties": { "expressionEvaluationOptions": { "scope": "inner" @@ -1541,32 +1688,54 @@ "parameters": { "groupIds": { "value": [ - "[parameters('privateEndpoints')[copyIndex()].service]" + "[coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].service]" ] }, - "name": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'name'), createObject('value', parameters('privateEndpoints')[copyIndex()].name), createObject('value', format('pe-{0}-{1}-{2}', last(split(resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('name')), '/')), parameters('privateEndpoints')[copyIndex()].service, copyIndex())))]", + "name": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'name'), format('pep-{0}-{1}-{2}', last(split(resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('name')), '/')), coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].service), copyIndex()))]" + }, "serviceResourceId": { "value": "[resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('name'))]" }, "subnetResourceId": { - "value": "[parameters('privateEndpoints')[copyIndex()].subnetResourceId]" + "value": "[coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId]" }, "enableDefaultTelemetry": { - "value": "[variables('enableReferencedModulesTelemetry')]" + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'enableDefaultTelemetry'), variables('enableReferencedModulesTelemetry'))]" + }, + "location": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'location'), reference(split(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location)]" }, - "location": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'location'), createObject('value', parameters('privateEndpoints')[copyIndex()].location), createObject('value', reference(split(parameters('privateEndpoints')[copyIndex()].subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location))]", "lock": { - "value": "[coalesce(tryGet(parameters('privateEndpoints')[copyIndex()], 'lock'), parameters('lock'))]" + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'lock'), parameters('lock'))]" + }, + "privateDnsZoneGroupName": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneGroupName')]" + }, + "privateDnsZoneResourceIds": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneResourceIds')]" + }, + "roleAssignments": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'roleAssignments')]" }, - "privateDnsZoneGroupName": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'privateDnsZoneGroupName'), createObject('value', parameters('privateEndpoints')[copyIndex()].privateDnsZoneGroupName), createObject('value', 'default'))]", - "privateDnsZoneResourceIds": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'privateDnsZoneResourceIds'), createObject('value', parameters('privateEndpoints')[copyIndex()].privateDnsZoneResourceIds), createObject('value', createArray()))]", - "roleAssignments": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'roleAssignments'), createObject('value', parameters('privateEndpoints')[copyIndex()].roleAssignments), createObject('value', createArray()))]", - "tags": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'tags'), createObject('value', parameters('privateEndpoints')[copyIndex()].tags), createObject('value', createObject()))]", - "manualPrivateLinkServiceConnections": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'manualPrivateLinkServiceConnections'), createObject('value', parameters('privateEndpoints')[copyIndex()].manualPrivateLinkServiceConnections), createObject('value', createArray()))]", - "customDnsConfigs": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'customDnsConfigs'), createObject('value', parameters('privateEndpoints')[copyIndex()].customDnsConfigs), createObject('value', createArray()))]", - "ipConfigurations": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'ipConfigurations'), createObject('value', parameters('privateEndpoints')[copyIndex()].ipConfigurations), createObject('value', createArray()))]", - "applicationSecurityGroupResourceIds": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'applicationSecurityGroupResourceIds'), createObject('value', parameters('privateEndpoints')[copyIndex()].applicationSecurityGroupResourceIds), createObject('value', createArray()))]", - "customNetworkInterfaceName": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'customNetworkInterfaceName'), createObject('value', parameters('privateEndpoints')[copyIndex()].customNetworkInterfaceName), createObject('value', ''))]" + "tags": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'tags'), parameters('tags'))]" + }, + "manualPrivateLinkServiceConnections": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'manualPrivateLinkServiceConnections')]" + }, + "customDnsConfigs": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customDnsConfigs')]" + }, + "ipConfigurations": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'ipConfigurations')]" + }, + "applicationSecurityGroupResourceIds": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'applicationSecurityGroupResourceIds')]" + }, + "customNetworkInterfaceName": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customNetworkInterfaceName')]" + } }, "template": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", diff --git a/modules/event-grid/domain/.test/pe/main.test.bicep b/modules/event-grid/domain/.test/pe/main.test.bicep index cd166546be..43c759532a 100644 --- a/modules/event-grid/domain/.test/pe/main.test.bicep +++ b/modules/event-grid/domain/.test/pe/main.test.bicep @@ -54,7 +54,6 @@ module testDeployment '../../main.bicep' = { privateDnsZoneResourceIds: [ nestedDependencies.outputs.privateDNSZoneResourceId ] - service: 'domain' subnetResourceId: nestedDependencies.outputs.subnetResourceId tags: { 'hidden-title': 'This is visible in the resource name' diff --git a/modules/event-grid/domain/README.md b/modules/event-grid/domain/README.md index 987ecbd5b0..99c32bd414 100644 --- a/modules/event-grid/domain/README.md +++ b/modules/event-grid/domain/README.md @@ -255,7 +255,6 @@ module domain 'br:bicep/modules/event-grid.domain:1.0.0' = { privateDnsZoneResourceIds: [ '' ] - service: 'domain' subnetResourceId: '' tags: { Environment: 'Non-Prod' @@ -299,7 +298,6 @@ module domain 'br:bicep/modules/event-grid.domain:1.0.0' = { "privateDnsZoneResourceIds": [ "" ], - "service": "domain", "subnetResourceId": "", "tags": { "Environment": "Non-Prod", @@ -479,7 +477,168 @@ The name of the Event Grid Domain. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible. - Required: No - Type: array -- Default: `[]` + + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`applicationSecurityGroupResourceIds`](#parameter-privateendpointsapplicationsecuritygroupresourceids) | No | array | Optional. Application security groups in which the private endpoint IP configuration is included. | +| [`customDnsConfigs`](#parameter-privateendpointscustomdnsconfigs) | No | array | Optional. Custom DNS configurations. | +| [`customNetworkInterfaceName`](#parameter-privateendpointscustomnetworkinterfacename) | No | string | Optional. The custom name of the network interface attached to the private endpoint. | +| [`enableTelemetry`](#parameter-privateendpointsenabletelemetry) | No | bool | Optional. Enable/Disable usage telemetry for module. | +| [`ipConfigurations`](#parameter-privateendpointsipconfigurations) | No | array | Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints. | +| [`location`](#parameter-privateendpointslocation) | No | string | Optional. The location to deploy the private endpoint to. | +| [`lock`](#parameter-privateendpointslock) | No | object | Optional. Specify the type of lock. | +| [`manualPrivateLinkServiceConnections`](#parameter-privateendpointsmanualprivatelinkserviceconnections) | No | array | Optional. Manual PrivateLink Service Connections. | +| [`name`](#parameter-privateendpointsname) | No | string | Optional. The name of the private endpoint. | +| [`privateDnsZoneGroupName`](#parameter-privateendpointsprivatednszonegroupname) | No | string | Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided. | +| [`privateDnsZoneResourceIds`](#parameter-privateendpointsprivatednszoneresourceids) | No | array | Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones. | +| [`roleAssignments`](#parameter-privateendpointsroleassignments) | No | array | 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'. | +| [`service`](#parameter-privateendpointsservice) | No | string | Optional. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob". | +| [`subnetResourceId`](#parameter-privateendpointssubnetresourceid) | Yes | string | Required. Resource ID of the subnet where the endpoint needs to be created. | +| [`tags`](#parameter-privateendpointstags) | No | object | Optional. Tags to be applied on all resources/resource groups in this deployment. | + +### Parameter: `privateEndpoints.applicationSecurityGroupResourceIds` + +Optional. Application security groups in which the private endpoint IP configuration is included. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.customDnsConfigs` + +Optional. Custom DNS configurations. + +- Required: No +- Type: array + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`fqdn`](#parameter-privateendpointscustomdnsconfigsfqdn) | No | string | | +| [`ipAddresses`](#parameter-privateendpointscustomdnsconfigsipaddresses) | Yes | array | | + +### Parameter: `privateEndpoints.customDnsConfigs.fqdn` +- Required: No +- Type: string + +### Parameter: `privateEndpoints.customDnsConfigs.ipAddresses` +- Required: Yes +- Type: array + + +### Parameter: `privateEndpoints.customNetworkInterfaceName` + +Optional. The custom name of the network interface attached to the private endpoint. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.enableTelemetry` + +Optional. Enable/Disable usage telemetry for module. + +- Required: No +- Type: bool + +### Parameter: `privateEndpoints.ipConfigurations` + +Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints. + +- Required: No +- Type: array + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`groupId`](#parameter-privateendpointsipconfigurationsgroupid) | Yes | string | | +| [`memberName`](#parameter-privateendpointsipconfigurationsmembername) | Yes | string | | +| [`name`](#parameter-privateendpointsipconfigurationsname) | Yes | string | | +| [`privateIpAddress`](#parameter-privateendpointsipconfigurationsprivateipaddress) | Yes | string | | + +### Parameter: `privateEndpoints.ipConfigurations.groupId` +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.ipConfigurations.memberName` +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.ipConfigurations.name` +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.ipConfigurations.privateIpAddress` +- Required: Yes +- Type: string + + +### Parameter: `privateEndpoints.location` + +Optional. The location to deploy the private endpoint to. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.lock` + +Optional. Specify the type of lock. + +- Required: No +- Type: object + +### Parameter: `privateEndpoints.manualPrivateLinkServiceConnections` + +Optional. Manual PrivateLink Service Connections. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.name` + +Optional. The name of the private endpoint. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.privateDnsZoneGroupName` + +Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.privateDnsZoneResourceIds` + +Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.roleAssignments` + +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'. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.service` + +Optional. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob". + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.subnetResourceId` + +Required. Resource ID of the subnet where the endpoint needs to be created. + +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.tags` + +Optional. Tags to be applied on all resources/resource groups in this deployment. + +- Required: No +- Type: object ### Parameter: `publicNetworkAccess` diff --git a/modules/event-grid/domain/main.bicep b/modules/event-grid/domain/main.bicep index 705dad53d0..51b317cac0 100644 --- a/modules/event-grid/domain/main.bicep +++ b/modules/event-grid/domain/main.bicep @@ -38,7 +38,7 @@ param diagnosticEventHubAuthorizationRuleId string = '' param diagnosticEventHubName string = '' @description('Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible.') -param privateEndpoints array = [] +param privateEndpoints privateEndpointType @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 roleAssignmentType @@ -165,27 +165,27 @@ resource domain_diagnosticSettings 'Microsoft.Insights/diagnosticsettings@2021-0 scope: domain } -module domain_privateEndpoints '../../network/private-endpoint/main.bicep' = [for (privateEndpoint, index) in privateEndpoints: { - name: '${uniqueString(deployment().name, location)}-Domain-PrivateEndpoint-${index}' +module domain_privateEndpoints '../../network/private-endpoint/main.bicep' = [for (privateEndpoint, index) in (privateEndpoints ?? []): { + name: '${uniqueString(deployment().name, location)}-domain-PrivateEndpoint-${index}' params: { groupIds: [ - privateEndpoint.service + privateEndpoint.?service ?? 'domain' ] - name: contains(privateEndpoint, 'name') ? privateEndpoint.name : 'pe-${last(split(domain.id, '/'))}-${privateEndpoint.service}-${index}' + name: privateEndpoint.?name ?? 'pep-${last(split(domain.id, '/'))}-${privateEndpoint.?service ?? 'domain'}-${index}' serviceResourceId: domain.id subnetResourceId: privateEndpoint.subnetResourceId - enableDefaultTelemetry: enableReferencedModulesTelemetry - location: contains(privateEndpoint, 'location') ? privateEndpoint.location : reference(split(privateEndpoint.subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location + enableDefaultTelemetry: privateEndpoint.?enableDefaultTelemetry ?? enableReferencedModulesTelemetry + location: privateEndpoint.?location ?? reference(split(privateEndpoint.subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location lock: privateEndpoint.?lock ?? lock - privateDnsZoneGroupName: contains(privateEndpoint, 'privateDnsZoneGroupName') ? privateEndpoint.privateDnsZoneGroupName : 'default' - privateDnsZoneResourceIds: contains(privateEndpoint, 'privateDnsZoneResourceIds') ? privateEndpoint.privateDnsZoneResourceIds : [] - 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 : [] - applicationSecurityGroupResourceIds: contains(privateEndpoint, 'applicationSecurityGroupResourceIds') ? privateEndpoint.applicationSecurityGroupResourceIds : [] - customNetworkInterfaceName: contains(privateEndpoint, 'customNetworkInterfaceName') ? privateEndpoint.customNetworkInterfaceName : '' + privateDnsZoneGroupName: privateEndpoint.?privateDnsZoneGroupName + privateDnsZoneResourceIds: privateEndpoint.?privateDnsZoneResourceIds + roleAssignments: privateEndpoint.?roleAssignments + tags: privateEndpoint.?tags ?? tags + manualPrivateLinkServiceConnections: privateEndpoint.?manualPrivateLinkServiceConnections + customDnsConfigs: privateEndpoint.?customDnsConfigs + ipConfigurations: privateEndpoint.?ipConfigurations + applicationSecurityGroupResourceIds: privateEndpoint.?applicationSecurityGroupResourceIds + customNetworkInterfaceName: privateEndpoint.?customNetworkInterfaceName } }] @@ -249,3 +249,58 @@ type roleAssignmentType = { @description('Optional. The Resource Id of the delegated managed identity resource.') delegatedManagedIdentityResourceId: string? }[]? + +type privateEndpointType = { + @description('Optional. The name of the private endpoint.') + name: string? + + @description('Optional. The location to deploy the private endpoint to.') + location: string? + + @description('Optional. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob".') + service: string? + + @description('Required. Resource ID of the subnet where the endpoint needs to be created.') + subnetResourceId: string + + @description('Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided.') + privateDnsZoneGroupName: string? + + @description('Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones.') + privateDnsZoneResourceIds: string[]? + + @description('Optional. Custom DNS configurations.') + customDnsConfigs: { + fqdn: string? + ipAddresses: string[] + }[]? + + @description('Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints.') + ipConfigurations: { + name: string + groupId: string + memberName: string + privateIpAddress: string + }[]? + + @description('Optional. Application security groups in which the private endpoint IP configuration is included.') + applicationSecurityGroupResourceIds: string[]? + + @description('Optional. The custom name of the network interface attached to the private endpoint.') + customNetworkInterfaceName: string? + + @description('Optional. Specify the type of lock.') + lock: lockType + + @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\'.') + roleAssignments: roleAssignmentType + + @description('Optional. Tags to be applied on all resources/resource groups in this deployment.') + tags: object? + + @description('Optional. Manual PrivateLink Service Connections.') + manualPrivateLinkServiceConnections: array? + + @description('Optional. Enable/Disable usage telemetry for module.') + enableTelemetry: bool? +}[]? diff --git a/modules/event-grid/domain/main.json b/modules/event-grid/domain/main.json index b9500a20b8..a6648e2540 100644 --- a/modules/event-grid/domain/main.json +++ b/modules/event-grid/domain/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.22.6.54827", - "templateHash": "5102513293970152919" + "templateHash": "1139242141774790759" }, "name": "Event Grid Domains", "description": "This module deploys an Event Grid Domain.", @@ -103,6 +103,155 @@ } }, "nullable": true + }, + "privateEndpointType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private endpoint." + } + }, + "location": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The location to deploy the private endpoint to." + } + }, + "service": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The service (sub-) type to deploy the private endpoint for. For example \"vault\" or \"blob\"." + } + }, + "subnetResourceId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the subnet where the endpoint needs to be created." + } + }, + "privateDnsZoneGroupName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided." + } + }, + "privateDnsZoneResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones." + } + }, + "customDnsConfigs": { + "type": "array", + "items": { + "type": "object", + "properties": { + "fqdn": { + "type": "string", + "nullable": true + }, + "ipAddresses": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. Custom DNS configurations." + } + }, + "ipConfigurations": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "groupId": { + "type": "string" + }, + "memberName": { + "type": "string" + }, + "privateIpAddress": { + "type": "string" + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints." + } + }, + "applicationSecurityGroupResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. Application security groups in which the private endpoint IP configuration is included." + } + }, + "customNetworkInterfaceName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The custom name of the network interface attached to the private endpoint." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "metadata": { + "description": "Optional. Specify the type of lock." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "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'." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags to be applied on all resources/resource groups in this deployment." + } + }, + "manualPrivateLinkServiceConnections": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. Manual PrivateLink Service Connections." + } + }, + "enableTelemetry": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + } + } + }, + "nullable": true } }, "parameters": { @@ -181,8 +330,7 @@ } }, "privateEndpoints": { - "type": "array", - "defaultValue": [], + "$ref": "#/definitions/privateEndpointType", "metadata": { "description": "Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible." } @@ -492,11 +640,11 @@ "domain_privateEndpoints": { "copy": { "name": "domain_privateEndpoints", - "count": "[length(parameters('privateEndpoints'))]" + "count": "[length(coalesce(parameters('privateEndpoints'), createArray()))]" }, "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", - "name": "[format('{0}-Domain-PrivateEndpoint-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "name": "[format('{0}-domain-PrivateEndpoint-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", "properties": { "expressionEvaluationOptions": { "scope": "inner" @@ -505,32 +653,54 @@ "parameters": { "groupIds": { "value": [ - "[parameters('privateEndpoints')[copyIndex()].service]" + "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'domain')]" ] }, - "name": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'name'), createObject('value', parameters('privateEndpoints')[copyIndex()].name), createObject('value', format('pe-{0}-{1}-{2}', last(split(resourceId('Microsoft.EventGrid/domains', parameters('name')), '/')), parameters('privateEndpoints')[copyIndex()].service, copyIndex())))]", + "name": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'name'), format('pep-{0}-{1}-{2}', last(split(resourceId('Microsoft.EventGrid/domains', parameters('name')), '/')), coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'domain'), copyIndex()))]" + }, "serviceResourceId": { "value": "[resourceId('Microsoft.EventGrid/domains', parameters('name'))]" }, "subnetResourceId": { - "value": "[parameters('privateEndpoints')[copyIndex()].subnetResourceId]" + "value": "[coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId]" }, "enableDefaultTelemetry": { - "value": "[variables('enableReferencedModulesTelemetry')]" + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'enableDefaultTelemetry'), variables('enableReferencedModulesTelemetry'))]" + }, + "location": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'location'), reference(split(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location)]" }, - "location": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'location'), createObject('value', parameters('privateEndpoints')[copyIndex()].location), createObject('value', reference(split(parameters('privateEndpoints')[copyIndex()].subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location))]", "lock": { - "value": "[coalesce(tryGet(parameters('privateEndpoints')[copyIndex()], 'lock'), parameters('lock'))]" + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'lock'), parameters('lock'))]" + }, + "privateDnsZoneGroupName": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneGroupName')]" + }, + "privateDnsZoneResourceIds": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneResourceIds')]" + }, + "roleAssignments": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'roleAssignments')]" }, - "privateDnsZoneGroupName": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'privateDnsZoneGroupName'), createObject('value', parameters('privateEndpoints')[copyIndex()].privateDnsZoneGroupName), createObject('value', 'default'))]", - "privateDnsZoneResourceIds": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'privateDnsZoneResourceIds'), createObject('value', parameters('privateEndpoints')[copyIndex()].privateDnsZoneResourceIds), createObject('value', createArray()))]", - "roleAssignments": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'roleAssignments'), createObject('value', parameters('privateEndpoints')[copyIndex()].roleAssignments), createObject('value', createArray()))]", - "tags": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'tags'), createObject('value', parameters('privateEndpoints')[copyIndex()].tags), createObject('value', createObject()))]", - "manualPrivateLinkServiceConnections": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'manualPrivateLinkServiceConnections'), createObject('value', parameters('privateEndpoints')[copyIndex()].manualPrivateLinkServiceConnections), createObject('value', createArray()))]", - "customDnsConfigs": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'customDnsConfigs'), createObject('value', parameters('privateEndpoints')[copyIndex()].customDnsConfigs), createObject('value', createArray()))]", - "ipConfigurations": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'ipConfigurations'), createObject('value', parameters('privateEndpoints')[copyIndex()].ipConfigurations), createObject('value', createArray()))]", - "applicationSecurityGroupResourceIds": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'applicationSecurityGroupResourceIds'), createObject('value', parameters('privateEndpoints')[copyIndex()].applicationSecurityGroupResourceIds), createObject('value', createArray()))]", - "customNetworkInterfaceName": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'customNetworkInterfaceName'), createObject('value', parameters('privateEndpoints')[copyIndex()].customNetworkInterfaceName), createObject('value', ''))]" + "tags": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'tags'), parameters('tags'))]" + }, + "manualPrivateLinkServiceConnections": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'manualPrivateLinkServiceConnections')]" + }, + "customDnsConfigs": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customDnsConfigs')]" + }, + "ipConfigurations": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'ipConfigurations')]" + }, + "applicationSecurityGroupResourceIds": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'applicationSecurityGroupResourceIds')]" + }, + "customNetworkInterfaceName": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customNetworkInterfaceName')]" + } }, "template": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", diff --git a/modules/event-grid/topic/.test/pe/main.test.bicep b/modules/event-grid/topic/.test/pe/main.test.bicep index 377965d0ec..a70b97b936 100644 --- a/modules/event-grid/topic/.test/pe/main.test.bicep +++ b/modules/event-grid/topic/.test/pe/main.test.bicep @@ -54,7 +54,6 @@ module testDeployment '../../main.bicep' = { privateDnsZoneResourceIds: [ nestedDependencies.outputs.privateDNSZoneResourceId ] - service: 'topic' subnetResourceId: nestedDependencies.outputs.subnetResourceId tags: { 'hidden-title': 'This is visible in the resource name' diff --git a/modules/event-grid/topic/README.md b/modules/event-grid/topic/README.md index ea41b5b492..edcf1c9672 100644 --- a/modules/event-grid/topic/README.md +++ b/modules/event-grid/topic/README.md @@ -297,7 +297,6 @@ module topic 'br:bicep/modules/event-grid.topic:1.0.0' = { privateDnsZoneResourceIds: [ '' ] - service: 'topic' subnetResourceId: '' tags: { Environment: 'Non-Prod' @@ -341,7 +340,6 @@ module topic 'br:bicep/modules/event-grid.topic:1.0.0' = { "privateDnsZoneResourceIds": [ "" ], - "service": "topic", "subnetResourceId": "", "tags": { "Environment": "Non-Prod", @@ -512,7 +510,168 @@ The name of the Event Grid Topic. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible. - Required: No - Type: array -- Default: `[]` + + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`applicationSecurityGroupResourceIds`](#parameter-privateendpointsapplicationsecuritygroupresourceids) | No | array | Optional. Application security groups in which the private endpoint IP configuration is included. | +| [`customDnsConfigs`](#parameter-privateendpointscustomdnsconfigs) | No | array | Optional. Custom DNS configurations. | +| [`customNetworkInterfaceName`](#parameter-privateendpointscustomnetworkinterfacename) | No | string | Optional. The custom name of the network interface attached to the private endpoint. | +| [`enableTelemetry`](#parameter-privateendpointsenabletelemetry) | No | bool | Optional. Enable/Disable usage telemetry for module. | +| [`ipConfigurations`](#parameter-privateendpointsipconfigurations) | No | array | Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints. | +| [`location`](#parameter-privateendpointslocation) | No | string | Optional. The location to deploy the private endpoint to. | +| [`lock`](#parameter-privateendpointslock) | No | object | Optional. Specify the type of lock. | +| [`manualPrivateLinkServiceConnections`](#parameter-privateendpointsmanualprivatelinkserviceconnections) | No | array | Optional. Manual PrivateLink Service Connections. | +| [`name`](#parameter-privateendpointsname) | No | string | Optional. The name of the private endpoint. | +| [`privateDnsZoneGroupName`](#parameter-privateendpointsprivatednszonegroupname) | No | string | Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided. | +| [`privateDnsZoneResourceIds`](#parameter-privateendpointsprivatednszoneresourceids) | No | array | Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones. | +| [`roleAssignments`](#parameter-privateendpointsroleassignments) | No | array | 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'. | +| [`service`](#parameter-privateendpointsservice) | No | string | Optional. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob". | +| [`subnetResourceId`](#parameter-privateendpointssubnetresourceid) | Yes | string | Required. Resource ID of the subnet where the endpoint needs to be created. | +| [`tags`](#parameter-privateendpointstags) | No | object | Optional. Tags to be applied on all resources/resource groups in this deployment. | + +### Parameter: `privateEndpoints.applicationSecurityGroupResourceIds` + +Optional. Application security groups in which the private endpoint IP configuration is included. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.customDnsConfigs` + +Optional. Custom DNS configurations. + +- Required: No +- Type: array + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`fqdn`](#parameter-privateendpointscustomdnsconfigsfqdn) | No | string | | +| [`ipAddresses`](#parameter-privateendpointscustomdnsconfigsipaddresses) | Yes | array | | + +### Parameter: `privateEndpoints.customDnsConfigs.fqdn` +- Required: No +- Type: string + +### Parameter: `privateEndpoints.customDnsConfigs.ipAddresses` +- Required: Yes +- Type: array + + +### Parameter: `privateEndpoints.customNetworkInterfaceName` + +Optional. The custom name of the network interface attached to the private endpoint. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.enableTelemetry` + +Optional. Enable/Disable usage telemetry for module. + +- Required: No +- Type: bool + +### Parameter: `privateEndpoints.ipConfigurations` + +Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints. + +- Required: No +- Type: array + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`groupId`](#parameter-privateendpointsipconfigurationsgroupid) | Yes | string | | +| [`memberName`](#parameter-privateendpointsipconfigurationsmembername) | Yes | string | | +| [`name`](#parameter-privateendpointsipconfigurationsname) | Yes | string | | +| [`privateIpAddress`](#parameter-privateendpointsipconfigurationsprivateipaddress) | Yes | string | | + +### Parameter: `privateEndpoints.ipConfigurations.groupId` +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.ipConfigurations.memberName` +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.ipConfigurations.name` +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.ipConfigurations.privateIpAddress` +- Required: Yes +- Type: string + + +### Parameter: `privateEndpoints.location` + +Optional. The location to deploy the private endpoint to. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.lock` + +Optional. Specify the type of lock. + +- Required: No +- Type: object + +### Parameter: `privateEndpoints.manualPrivateLinkServiceConnections` + +Optional. Manual PrivateLink Service Connections. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.name` + +Optional. The name of the private endpoint. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.privateDnsZoneGroupName` + +Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.privateDnsZoneResourceIds` + +Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.roleAssignments` + +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'. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.service` + +Optional. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob". + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.subnetResourceId` + +Required. Resource ID of the subnet where the endpoint needs to be created. + +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.tags` + +Optional. Tags to be applied on all resources/resource groups in this deployment. + +- Required: No +- Type: object ### Parameter: `publicNetworkAccess` diff --git a/modules/event-grid/topic/main.bicep b/modules/event-grid/topic/main.bicep index f0ee5f204b..89d93f0f2b 100644 --- a/modules/event-grid/topic/main.bicep +++ b/modules/event-grid/topic/main.bicep @@ -35,7 +35,7 @@ param diagnosticEventHubAuthorizationRuleId string = '' param diagnosticEventHubName string = '' @description('Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible.') -param privateEndpoints array = [] +param privateEndpoints privateEndpointType @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 roleAssignmentType @@ -167,27 +167,27 @@ resource topic_diagnosticSettings 'Microsoft.Insights/diagnosticsettings@2021-05 scope: topic } -module topic_privateEndpoints '../../network/private-endpoint/main.bicep' = [for (privateEndpoint, index) in privateEndpoints: { - name: '${uniqueString(deployment().name, location)}-Topic-PrivateEndpoint-${index}' +module topic_privateEndpoints '../../network/private-endpoint/main.bicep' = [for (privateEndpoint, index) in (privateEndpoints ?? []): { + name: '${uniqueString(deployment().name, location)}-topic-PrivateEndpoint-${index}' params: { groupIds: [ - privateEndpoint.service + privateEndpoint.?service ?? 'topic' ] - name: contains(privateEndpoint, 'name') ? privateEndpoint.name : 'pe-${last(split(topic.id, '/'))}-${privateEndpoint.service}-${index}' + name: privateEndpoint.?name ?? 'pep-${last(split(topic.id, '/'))}-${privateEndpoint.?service ?? 'topic'}-${index}' serviceResourceId: topic.id subnetResourceId: privateEndpoint.subnetResourceId - enableDefaultTelemetry: enableReferencedModulesTelemetry - location: contains(privateEndpoint, 'location') ? privateEndpoint.location : reference(split(privateEndpoint.subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location + enableDefaultTelemetry: privateEndpoint.?enableDefaultTelemetry ?? enableReferencedModulesTelemetry + location: privateEndpoint.?location ?? reference(split(privateEndpoint.subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location lock: privateEndpoint.?lock ?? lock - privateDnsZoneGroupName: contains(privateEndpoint, 'privateDnsZoneGroupName') ? privateEndpoint.privateDnsZoneGroupName : 'default' - privateDnsZoneResourceIds: contains(privateEndpoint, 'privateDnsZoneResourceIds') ? privateEndpoint.privateDnsZoneResourceIds : [] - 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 : [] - applicationSecurityGroupResourceIds: contains(privateEndpoint, 'applicationSecurityGroupResourceIds') ? privateEndpoint.applicationSecurityGroupResourceIds : [] - customNetworkInterfaceName: contains(privateEndpoint, 'customNetworkInterfaceName') ? privateEndpoint.customNetworkInterfaceName : '' + privateDnsZoneGroupName: privateEndpoint.?privateDnsZoneGroupName + privateDnsZoneResourceIds: privateEndpoint.?privateDnsZoneResourceIds + roleAssignments: privateEndpoint.?roleAssignments + tags: privateEndpoint.?tags ?? tags + manualPrivateLinkServiceConnections: privateEndpoint.?manualPrivateLinkServiceConnections + customDnsConfigs: privateEndpoint.?customDnsConfigs + ipConfigurations: privateEndpoint.?ipConfigurations + applicationSecurityGroupResourceIds: privateEndpoint.?applicationSecurityGroupResourceIds + customNetworkInterfaceName: privateEndpoint.?customNetworkInterfaceName } }] @@ -251,3 +251,58 @@ type roleAssignmentType = { @description('Optional. The Resource Id of the delegated managed identity resource.') delegatedManagedIdentityResourceId: string? }[]? + +type privateEndpointType = { + @description('Optional. The name of the private endpoint.') + name: string? + + @description('Optional. The location to deploy the private endpoint to.') + location: string? + + @description('Optional. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob".') + service: string? + + @description('Required. Resource ID of the subnet where the endpoint needs to be created.') + subnetResourceId: string + + @description('Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided.') + privateDnsZoneGroupName: string? + + @description('Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones.') + privateDnsZoneResourceIds: string[]? + + @description('Optional. Custom DNS configurations.') + customDnsConfigs: { + fqdn: string? + ipAddresses: string[] + }[]? + + @description('Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints.') + ipConfigurations: { + name: string + groupId: string + memberName: string + privateIpAddress: string + }[]? + + @description('Optional. Application security groups in which the private endpoint IP configuration is included.') + applicationSecurityGroupResourceIds: string[]? + + @description('Optional. The custom name of the network interface attached to the private endpoint.') + customNetworkInterfaceName: string? + + @description('Optional. Specify the type of lock.') + lock: lockType + + @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\'.') + roleAssignments: roleAssignmentType + + @description('Optional. Tags to be applied on all resources/resource groups in this deployment.') + tags: object? + + @description('Optional. Manual PrivateLink Service Connections.') + manualPrivateLinkServiceConnections: array? + + @description('Optional. Enable/Disable usage telemetry for module.') + enableTelemetry: bool? +}[]? diff --git a/modules/event-grid/topic/main.json b/modules/event-grid/topic/main.json index eff81c7e61..0aadfa26de 100644 --- a/modules/event-grid/topic/main.json +++ b/modules/event-grid/topic/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.22.6.54827", - "templateHash": "875855876117363195" + "templateHash": "12386573545698498000" }, "name": "Event Grid Topics", "description": "This module deploys an Event Grid Topic.", @@ -103,6 +103,155 @@ } }, "nullable": true + }, + "privateEndpointType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private endpoint." + } + }, + "location": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The location to deploy the private endpoint to." + } + }, + "service": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The service (sub-) type to deploy the private endpoint for. For example \"vault\" or \"blob\"." + } + }, + "subnetResourceId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the subnet where the endpoint needs to be created." + } + }, + "privateDnsZoneGroupName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided." + } + }, + "privateDnsZoneResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones." + } + }, + "customDnsConfigs": { + "type": "array", + "items": { + "type": "object", + "properties": { + "fqdn": { + "type": "string", + "nullable": true + }, + "ipAddresses": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. Custom DNS configurations." + } + }, + "ipConfigurations": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "groupId": { + "type": "string" + }, + "memberName": { + "type": "string" + }, + "privateIpAddress": { + "type": "string" + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints." + } + }, + "applicationSecurityGroupResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. Application security groups in which the private endpoint IP configuration is included." + } + }, + "customNetworkInterfaceName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The custom name of the network interface attached to the private endpoint." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "metadata": { + "description": "Optional. Specify the type of lock." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "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'." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags to be applied on all resources/resource groups in this deployment." + } + }, + "manualPrivateLinkServiceConnections": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. Manual PrivateLink Service Connections." + } + }, + "enableTelemetry": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + } + } + }, + "nullable": true } }, "parameters": { @@ -174,8 +323,7 @@ } }, "privateEndpoints": { - "type": "array", - "defaultValue": [], + "$ref": "#/definitions/privateEndpointType", "metadata": { "description": "Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible." } @@ -569,11 +717,11 @@ "topic_privateEndpoints": { "copy": { "name": "topic_privateEndpoints", - "count": "[length(parameters('privateEndpoints'))]" + "count": "[length(coalesce(parameters('privateEndpoints'), createArray()))]" }, "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", - "name": "[format('{0}-Topic-PrivateEndpoint-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "name": "[format('{0}-topic-PrivateEndpoint-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", "properties": { "expressionEvaluationOptions": { "scope": "inner" @@ -582,32 +730,54 @@ "parameters": { "groupIds": { "value": [ - "[parameters('privateEndpoints')[copyIndex()].service]" + "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'topic')]" ] }, - "name": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'name'), createObject('value', parameters('privateEndpoints')[copyIndex()].name), createObject('value', format('pe-{0}-{1}-{2}', last(split(resourceId('Microsoft.EventGrid/topics', parameters('name')), '/')), parameters('privateEndpoints')[copyIndex()].service, copyIndex())))]", + "name": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'name'), format('pep-{0}-{1}-{2}', last(split(resourceId('Microsoft.EventGrid/topics', parameters('name')), '/')), coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'topic'), copyIndex()))]" + }, "serviceResourceId": { "value": "[resourceId('Microsoft.EventGrid/topics', parameters('name'))]" }, "subnetResourceId": { - "value": "[parameters('privateEndpoints')[copyIndex()].subnetResourceId]" + "value": "[coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId]" }, "enableDefaultTelemetry": { - "value": "[variables('enableReferencedModulesTelemetry')]" + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'enableDefaultTelemetry'), variables('enableReferencedModulesTelemetry'))]" + }, + "location": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'location'), reference(split(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location)]" }, - "location": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'location'), createObject('value', parameters('privateEndpoints')[copyIndex()].location), createObject('value', reference(split(parameters('privateEndpoints')[copyIndex()].subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location))]", "lock": { - "value": "[coalesce(tryGet(parameters('privateEndpoints')[copyIndex()], 'lock'), parameters('lock'))]" + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'lock'), parameters('lock'))]" + }, + "privateDnsZoneGroupName": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneGroupName')]" + }, + "privateDnsZoneResourceIds": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneResourceIds')]" + }, + "roleAssignments": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'roleAssignments')]" }, - "privateDnsZoneGroupName": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'privateDnsZoneGroupName'), createObject('value', parameters('privateEndpoints')[copyIndex()].privateDnsZoneGroupName), createObject('value', 'default'))]", - "privateDnsZoneResourceIds": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'privateDnsZoneResourceIds'), createObject('value', parameters('privateEndpoints')[copyIndex()].privateDnsZoneResourceIds), createObject('value', createArray()))]", - "roleAssignments": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'roleAssignments'), createObject('value', parameters('privateEndpoints')[copyIndex()].roleAssignments), createObject('value', createArray()))]", - "tags": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'tags'), createObject('value', parameters('privateEndpoints')[copyIndex()].tags), createObject('value', createObject()))]", - "manualPrivateLinkServiceConnections": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'manualPrivateLinkServiceConnections'), createObject('value', parameters('privateEndpoints')[copyIndex()].manualPrivateLinkServiceConnections), createObject('value', createArray()))]", - "customDnsConfigs": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'customDnsConfigs'), createObject('value', parameters('privateEndpoints')[copyIndex()].customDnsConfigs), createObject('value', createArray()))]", - "ipConfigurations": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'ipConfigurations'), createObject('value', parameters('privateEndpoints')[copyIndex()].ipConfigurations), createObject('value', createArray()))]", - "applicationSecurityGroupResourceIds": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'applicationSecurityGroupResourceIds'), createObject('value', parameters('privateEndpoints')[copyIndex()].applicationSecurityGroupResourceIds), createObject('value', createArray()))]", - "customNetworkInterfaceName": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'customNetworkInterfaceName'), createObject('value', parameters('privateEndpoints')[copyIndex()].customNetworkInterfaceName), createObject('value', ''))]" + "tags": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'tags'), parameters('tags'))]" + }, + "manualPrivateLinkServiceConnections": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'manualPrivateLinkServiceConnections')]" + }, + "customDnsConfigs": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customDnsConfigs')]" + }, + "ipConfigurations": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'ipConfigurations')]" + }, + "applicationSecurityGroupResourceIds": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'applicationSecurityGroupResourceIds')]" + }, + "customNetworkInterfaceName": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customNetworkInterfaceName')]" + } }, "template": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", diff --git a/modules/event-hub/namespace/.test/pe/main.test.bicep b/modules/event-hub/namespace/.test/pe/main.test.bicep index 66d56ba9f6..69b842c06e 100644 --- a/modules/event-hub/namespace/.test/pe/main.test.bicep +++ b/modules/event-hub/namespace/.test/pe/main.test.bicep @@ -57,7 +57,6 @@ module testDeployment '../../main.bicep' = { privateDnsZoneResourceIds: [ nestedDependencies.outputs.privateDNSZoneResourceId ] - service: 'namespace' subnetResourceId: nestedDependencies.outputs.subnetResourceId tags: { 'hidden-title': 'This is visible in the resource name' diff --git a/modules/event-hub/namespace/README.md b/modules/event-hub/namespace/README.md index 551da7d1b6..f77bda2132 100644 --- a/modules/event-hub/namespace/README.md +++ b/modules/event-hub/namespace/README.md @@ -580,7 +580,6 @@ module namespace 'br:bicep/modules/event-hub.namespace:1.0.0' = { privateDnsZoneResourceIds: [ '' ] - service: 'namespace' subnetResourceId: '' tags: { Environment: 'Non-Prod' @@ -627,7 +626,6 @@ module namespace 'br:bicep/modules/event-hub.namespace:1.0.0' = { "privateDnsZoneResourceIds": [ "" ], - "service": "namespace", "subnetResourceId": "", "tags": { "Environment": "Non-Prod", @@ -907,7 +905,168 @@ Configure networking options. This object contains IPs/Subnets to allow or restr Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible. - Required: No - Type: array -- Default: `[]` + + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`applicationSecurityGroupResourceIds`](#parameter-privateendpointsapplicationsecuritygroupresourceids) | No | array | Optional. Application security groups in which the private endpoint IP configuration is included. | +| [`customDnsConfigs`](#parameter-privateendpointscustomdnsconfigs) | No | array | Optional. Custom DNS configurations. | +| [`customNetworkInterfaceName`](#parameter-privateendpointscustomnetworkinterfacename) | No | string | Optional. The custom name of the network interface attached to the private endpoint. | +| [`enableTelemetry`](#parameter-privateendpointsenabletelemetry) | No | bool | Optional. Enable/Disable usage telemetry for module. | +| [`ipConfigurations`](#parameter-privateendpointsipconfigurations) | No | array | Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints. | +| [`location`](#parameter-privateendpointslocation) | No | string | Optional. The location to deploy the private endpoint to. | +| [`lock`](#parameter-privateendpointslock) | No | object | Optional. Specify the type of lock. | +| [`manualPrivateLinkServiceConnections`](#parameter-privateendpointsmanualprivatelinkserviceconnections) | No | array | Optional. Manual PrivateLink Service Connections. | +| [`name`](#parameter-privateendpointsname) | No | string | Optional. The name of the private endpoint. | +| [`privateDnsZoneGroupName`](#parameter-privateendpointsprivatednszonegroupname) | No | string | Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided. | +| [`privateDnsZoneResourceIds`](#parameter-privateendpointsprivatednszoneresourceids) | No | array | Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones. | +| [`roleAssignments`](#parameter-privateendpointsroleassignments) | No | array | 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'. | +| [`service`](#parameter-privateendpointsservice) | No | string | Optional. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob". | +| [`subnetResourceId`](#parameter-privateendpointssubnetresourceid) | Yes | string | Required. Resource ID of the subnet where the endpoint needs to be created. | +| [`tags`](#parameter-privateendpointstags) | No | object | Optional. Tags to be applied on all resources/resource groups in this deployment. | + +### Parameter: `privateEndpoints.applicationSecurityGroupResourceIds` + +Optional. Application security groups in which the private endpoint IP configuration is included. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.customDnsConfigs` + +Optional. Custom DNS configurations. + +- Required: No +- Type: array + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`fqdn`](#parameter-privateendpointscustomdnsconfigsfqdn) | No | string | | +| [`ipAddresses`](#parameter-privateendpointscustomdnsconfigsipaddresses) | Yes | array | | + +### Parameter: `privateEndpoints.customDnsConfigs.fqdn` +- Required: No +- Type: string + +### Parameter: `privateEndpoints.customDnsConfigs.ipAddresses` +- Required: Yes +- Type: array + + +### Parameter: `privateEndpoints.customNetworkInterfaceName` + +Optional. The custom name of the network interface attached to the private endpoint. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.enableTelemetry` + +Optional. Enable/Disable usage telemetry for module. + +- Required: No +- Type: bool + +### Parameter: `privateEndpoints.ipConfigurations` + +Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints. + +- Required: No +- Type: array + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`groupId`](#parameter-privateendpointsipconfigurationsgroupid) | Yes | string | | +| [`memberName`](#parameter-privateendpointsipconfigurationsmembername) | Yes | string | | +| [`name`](#parameter-privateendpointsipconfigurationsname) | Yes | string | | +| [`privateIpAddress`](#parameter-privateendpointsipconfigurationsprivateipaddress) | Yes | string | | + +### Parameter: `privateEndpoints.ipConfigurations.groupId` +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.ipConfigurations.memberName` +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.ipConfigurations.name` +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.ipConfigurations.privateIpAddress` +- Required: Yes +- Type: string + + +### Parameter: `privateEndpoints.location` + +Optional. The location to deploy the private endpoint to. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.lock` + +Optional. Specify the type of lock. + +- Required: No +- Type: object + +### Parameter: `privateEndpoints.manualPrivateLinkServiceConnections` + +Optional. Manual PrivateLink Service Connections. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.name` + +Optional. The name of the private endpoint. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.privateDnsZoneGroupName` + +Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.privateDnsZoneResourceIds` + +Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.roleAssignments` + +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'. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.service` + +Optional. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob". + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.subnetResourceId` + +Required. Resource ID of the subnet where the endpoint needs to be created. + +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.tags` + +Optional. Tags to be applied on all resources/resource groups in this deployment. + +- Required: No +- Type: object ### Parameter: `publicNetworkAccess` diff --git a/modules/event-hub/namespace/main.bicep b/modules/event-hub/namespace/main.bicep index c5a61777c1..b8ac4af79d 100644 --- a/modules/event-hub/namespace/main.bicep +++ b/modules/event-hub/namespace/main.bicep @@ -69,7 +69,7 @@ param minimumTlsVersion string = '1.2' param publicNetworkAccess string = '' @description('Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible.') -param privateEndpoints array = [] +param privateEndpoints privateEndpointType @description('Optional. Configure networking options. 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 = {} @@ -320,27 +320,27 @@ module eventHubNamespace_networkRuleSet 'network-rule-set/main.bicep' = if (!emp } } -module eventHubNamespace_privateEndpoints '../../network/private-endpoint/main.bicep' = [for (privateEndpoint, index) in privateEndpoints: { - name: '${uniqueString(deployment().name, location)}-EvhbNamespace-PrivateEndpoint-${index}' +module eventHubNamespace_privateEndpoints '../../network/private-endpoint/main.bicep' = [for (privateEndpoint, index) in (privateEndpoints ?? []): { + name: '${uniqueString(deployment().name, location)}-eventHubNamespace-PrivateEndpoint-${index}' params: { groupIds: [ - privateEndpoint.service + privateEndpoint.?service ?? 'namespace' ] - name: contains(privateEndpoint, 'name') ? privateEndpoint.name : 'pe-${last(split(eventHubNamespace.id, '/'))}-${privateEndpoint.service}-${index}' + name: privateEndpoint.?name ?? 'pep-${last(split(eventHubNamespace.id, '/'))}-${privateEndpoint.?service ?? 'namespace'}-${index}' serviceResourceId: eventHubNamespace.id subnetResourceId: privateEndpoint.subnetResourceId - enableDefaultTelemetry: enableReferencedModulesTelemetry - location: contains(privateEndpoint, 'location') ? privateEndpoint.location : reference(split(privateEndpoint.subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location + enableDefaultTelemetry: privateEndpoint.?enableDefaultTelemetry ?? enableReferencedModulesTelemetry + location: privateEndpoint.?location ?? reference(split(privateEndpoint.subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location lock: privateEndpoint.?lock ?? lock - privateDnsZoneGroupName: contains(privateEndpoint, 'privateDnsZoneGroupName') ? privateEndpoint.privateDnsZoneGroupName : 'default' - privateDnsZoneResourceIds: contains(privateEndpoint, 'privateDnsZoneResourceIds') ? privateEndpoint.privateDnsZoneResourceIds : [] - 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 : [] - applicationSecurityGroupResourceIds: contains(privateEndpoint, 'applicationSecurityGroupResourceIds') ? privateEndpoint.applicationSecurityGroupResourceIds : [] - customNetworkInterfaceName: contains(privateEndpoint, 'customNetworkInterfaceName') ? privateEndpoint.customNetworkInterfaceName : '' + privateDnsZoneGroupName: privateEndpoint.?privateDnsZoneGroupName + privateDnsZoneResourceIds: privateEndpoint.?privateDnsZoneResourceIds + roleAssignments: privateEndpoint.?roleAssignments + tags: privateEndpoint.?tags ?? tags + manualPrivateLinkServiceConnections: privateEndpoint.?manualPrivateLinkServiceConnections + customDnsConfigs: privateEndpoint.?customDnsConfigs + ipConfigurations: privateEndpoint.?ipConfigurations + applicationSecurityGroupResourceIds: privateEndpoint.?applicationSecurityGroupResourceIds + customNetworkInterfaceName: privateEndpoint.?customNetworkInterfaceName } }] @@ -429,3 +429,58 @@ type roleAssignmentType = { @description('Optional. The Resource Id of the delegated managed identity resource.') delegatedManagedIdentityResourceId: string? }[]? + +type privateEndpointType = { + @description('Optional. The name of the private endpoint.') + name: string? + + @description('Optional. The location to deploy the private endpoint to.') + location: string? + + @description('Optional. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob".') + service: string? + + @description('Required. Resource ID of the subnet where the endpoint needs to be created.') + subnetResourceId: string + + @description('Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided.') + privateDnsZoneGroupName: string? + + @description('Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones.') + privateDnsZoneResourceIds: string[]? + + @description('Optional. Custom DNS configurations.') + customDnsConfigs: { + fqdn: string? + ipAddresses: string[] + }[]? + + @description('Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints.') + ipConfigurations: { + name: string + groupId: string + memberName: string + privateIpAddress: string + }[]? + + @description('Optional. Application security groups in which the private endpoint IP configuration is included.') + applicationSecurityGroupResourceIds: string[]? + + @description('Optional. The custom name of the network interface attached to the private endpoint.') + customNetworkInterfaceName: string? + + @description('Optional. Specify the type of lock.') + lock: lockType + + @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\'.') + roleAssignments: roleAssignmentType + + @description('Optional. Tags to be applied on all resources/resource groups in this deployment.') + tags: object? + + @description('Optional. Manual PrivateLink Service Connections.') + manualPrivateLinkServiceConnections: array? + + @description('Optional. Enable/Disable usage telemetry for module.') + enableTelemetry: bool? +}[]? diff --git a/modules/event-hub/namespace/main.json b/modules/event-hub/namespace/main.json index 101a26a405..4f0df97e85 100644 --- a/modules/event-hub/namespace/main.json +++ b/modules/event-hub/namespace/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.22.6.54827", - "templateHash": "12601630852101639901" + "templateHash": "8192238306230963085" }, "name": "Event Hub Namespaces", "description": "This module deploys an Event Hub Namespace.", @@ -103,6 +103,155 @@ } }, "nullable": true + }, + "privateEndpointType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private endpoint." + } + }, + "location": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The location to deploy the private endpoint to." + } + }, + "service": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The service (sub-) type to deploy the private endpoint for. For example \"vault\" or \"blob\"." + } + }, + "subnetResourceId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the subnet where the endpoint needs to be created." + } + }, + "privateDnsZoneGroupName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided." + } + }, + "privateDnsZoneResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones." + } + }, + "customDnsConfigs": { + "type": "array", + "items": { + "type": "object", + "properties": { + "fqdn": { + "type": "string", + "nullable": true + }, + "ipAddresses": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. Custom DNS configurations." + } + }, + "ipConfigurations": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "groupId": { + "type": "string" + }, + "memberName": { + "type": "string" + }, + "privateIpAddress": { + "type": "string" + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints." + } + }, + "applicationSecurityGroupResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. Application security groups in which the private endpoint IP configuration is included." + } + }, + "customNetworkInterfaceName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The custom name of the network interface attached to the private endpoint." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "metadata": { + "description": "Optional. Specify the type of lock." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "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'." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags to be applied on all resources/resource groups in this deployment." + } + }, + "manualPrivateLinkServiceConnections": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. Manual PrivateLink Service Connections." + } + }, + "enableTelemetry": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + } + } + }, + "nullable": true } }, "parameters": { @@ -220,8 +369,7 @@ } }, "privateEndpoints": { - "type": "array", - "defaultValue": [], + "$ref": "#/definitions/privateEndpointType", "metadata": { "description": "Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible." } @@ -1702,11 +1850,11 @@ "eventHubNamespace_privateEndpoints": { "copy": { "name": "eventHubNamespace_privateEndpoints", - "count": "[length(parameters('privateEndpoints'))]" + "count": "[length(coalesce(parameters('privateEndpoints'), createArray()))]" }, "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", - "name": "[format('{0}-EvhbNamespace-PrivateEndpoint-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "name": "[format('{0}-eventHubNamespace-PrivateEndpoint-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", "properties": { "expressionEvaluationOptions": { "scope": "inner" @@ -1715,32 +1863,54 @@ "parameters": { "groupIds": { "value": [ - "[parameters('privateEndpoints')[copyIndex()].service]" + "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'namespace')]" ] }, - "name": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'name'), createObject('value', parameters('privateEndpoints')[copyIndex()].name), createObject('value', format('pe-{0}-{1}-{2}', last(split(resourceId('Microsoft.EventHub/namespaces', parameters('name')), '/')), parameters('privateEndpoints')[copyIndex()].service, copyIndex())))]", + "name": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'name'), format('pep-{0}-{1}-{2}', last(split(resourceId('Microsoft.EventHub/namespaces', parameters('name')), '/')), coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'namespace'), copyIndex()))]" + }, "serviceResourceId": { "value": "[resourceId('Microsoft.EventHub/namespaces', parameters('name'))]" }, "subnetResourceId": { - "value": "[parameters('privateEndpoints')[copyIndex()].subnetResourceId]" + "value": "[coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId]" }, "enableDefaultTelemetry": { - "value": "[variables('enableReferencedModulesTelemetry')]" + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'enableDefaultTelemetry'), variables('enableReferencedModulesTelemetry'))]" + }, + "location": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'location'), reference(split(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location)]" }, - "location": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'location'), createObject('value', parameters('privateEndpoints')[copyIndex()].location), createObject('value', reference(split(parameters('privateEndpoints')[copyIndex()].subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location))]", "lock": { - "value": "[coalesce(tryGet(parameters('privateEndpoints')[copyIndex()], 'lock'), parameters('lock'))]" + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'lock'), parameters('lock'))]" + }, + "privateDnsZoneGroupName": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneGroupName')]" + }, + "privateDnsZoneResourceIds": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneResourceIds')]" + }, + "roleAssignments": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'roleAssignments')]" }, - "privateDnsZoneGroupName": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'privateDnsZoneGroupName'), createObject('value', parameters('privateEndpoints')[copyIndex()].privateDnsZoneGroupName), createObject('value', 'default'))]", - "privateDnsZoneResourceIds": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'privateDnsZoneResourceIds'), createObject('value', parameters('privateEndpoints')[copyIndex()].privateDnsZoneResourceIds), createObject('value', createArray()))]", - "roleAssignments": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'roleAssignments'), createObject('value', parameters('privateEndpoints')[copyIndex()].roleAssignments), createObject('value', createArray()))]", - "tags": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'tags'), createObject('value', parameters('privateEndpoints')[copyIndex()].tags), createObject('value', createObject()))]", - "manualPrivateLinkServiceConnections": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'manualPrivateLinkServiceConnections'), createObject('value', parameters('privateEndpoints')[copyIndex()].manualPrivateLinkServiceConnections), createObject('value', createArray()))]", - "customDnsConfigs": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'customDnsConfigs'), createObject('value', parameters('privateEndpoints')[copyIndex()].customDnsConfigs), createObject('value', createArray()))]", - "ipConfigurations": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'ipConfigurations'), createObject('value', parameters('privateEndpoints')[copyIndex()].ipConfigurations), createObject('value', createArray()))]", - "applicationSecurityGroupResourceIds": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'applicationSecurityGroupResourceIds'), createObject('value', parameters('privateEndpoints')[copyIndex()].applicationSecurityGroupResourceIds), createObject('value', createArray()))]", - "customNetworkInterfaceName": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'customNetworkInterfaceName'), createObject('value', parameters('privateEndpoints')[copyIndex()].customNetworkInterfaceName), createObject('value', ''))]" + "tags": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'tags'), parameters('tags'))]" + }, + "manualPrivateLinkServiceConnections": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'manualPrivateLinkServiceConnections')]" + }, + "customDnsConfigs": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customDnsConfigs')]" + }, + "ipConfigurations": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'ipConfigurations')]" + }, + "applicationSecurityGroupResourceIds": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'applicationSecurityGroupResourceIds')]" + }, + "customNetworkInterfaceName": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customNetworkInterfaceName')]" + } }, "template": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", diff --git a/modules/insights/private-link-scope/.test/common/main.test.bicep b/modules/insights/private-link-scope/.test/common/main.test.bicep index 373e51553f..1622c7b7a0 100644 --- a/modules/insights/private-link-scope/.test/common/main.test.bicep +++ b/modules/insights/private-link-scope/.test/common/main.test.bicep @@ -63,11 +63,8 @@ module testDeployment '../../main.bicep' = { privateEndpoints: [ { privateDnsZoneResourceIds: [ - nestedDependencies.outputs.privateDNSZoneResourceId - ] - service: 'azuremonitor' subnetResourceId: nestedDependencies.outputs.subnetResourceId tags: { 'hidden-title': 'This is visible in the resource name' diff --git a/modules/insights/private-link-scope/README.md b/modules/insights/private-link-scope/README.md index 14f386ae96..503da87cab 100644 --- a/modules/insights/private-link-scope/README.md +++ b/modules/insights/private-link-scope/README.md @@ -53,7 +53,6 @@ This instance deploys the module with most of its features enabled. privateDnsZoneResourceIds: [ '' ] - service: 'azuremonitor' subnetResourceId: '' tags: { Environment: 'Non-Prod' @@ -110,7 +109,6 @@ This instance deploys the module with most of its features enabled. "privateDnsZoneResourceIds": [ "" ], - "service": "azuremonitor", "subnetResourceId": "", "tags": { "Environment": "Non-Prod", @@ -271,7 +269,168 @@ Name of the private link scope. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible. - Required: No - Type: array -- Default: `[]` + + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`applicationSecurityGroupResourceIds`](#parameter-privateendpointsapplicationsecuritygroupresourceids) | No | array | Optional. Application security groups in which the private endpoint IP configuration is included. | +| [`customDnsConfigs`](#parameter-privateendpointscustomdnsconfigs) | No | array | Optional. Custom DNS configurations. | +| [`customNetworkInterfaceName`](#parameter-privateendpointscustomnetworkinterfacename) | No | string | Optional. The custom name of the network interface attached to the private endpoint. | +| [`enableTelemetry`](#parameter-privateendpointsenabletelemetry) | No | bool | Optional. Enable/Disable usage telemetry for module. | +| [`ipConfigurations`](#parameter-privateendpointsipconfigurations) | No | array | Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints. | +| [`location`](#parameter-privateendpointslocation) | No | string | Optional. The location to deploy the private endpoint to. | +| [`lock`](#parameter-privateendpointslock) | No | object | Optional. Specify the type of lock. | +| [`manualPrivateLinkServiceConnections`](#parameter-privateendpointsmanualprivatelinkserviceconnections) | No | array | Optional. Manual PrivateLink Service Connections. | +| [`name`](#parameter-privateendpointsname) | No | string | Optional. The name of the private endpoint. | +| [`privateDnsZoneGroupName`](#parameter-privateendpointsprivatednszonegroupname) | No | string | Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided. | +| [`privateDnsZoneResourceIds`](#parameter-privateendpointsprivatednszoneresourceids) | No | array | Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones. | +| [`roleAssignments`](#parameter-privateendpointsroleassignments) | No | array | 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'. | +| [`service`](#parameter-privateendpointsservice) | No | string | Optional. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob". | +| [`subnetResourceId`](#parameter-privateendpointssubnetresourceid) | Yes | string | Required. Resource ID of the subnet where the endpoint needs to be created. | +| [`tags`](#parameter-privateendpointstags) | No | object | Optional. Tags to be applied on all resources/resource groups in this deployment. | + +### Parameter: `privateEndpoints.applicationSecurityGroupResourceIds` + +Optional. Application security groups in which the private endpoint IP configuration is included. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.customDnsConfigs` + +Optional. Custom DNS configurations. + +- Required: No +- Type: array + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`fqdn`](#parameter-privateendpointscustomdnsconfigsfqdn) | No | string | | +| [`ipAddresses`](#parameter-privateendpointscustomdnsconfigsipaddresses) | Yes | array | | + +### Parameter: `privateEndpoints.customDnsConfigs.fqdn` +- Required: No +- Type: string + +### Parameter: `privateEndpoints.customDnsConfigs.ipAddresses` +- Required: Yes +- Type: array + + +### Parameter: `privateEndpoints.customNetworkInterfaceName` + +Optional. The custom name of the network interface attached to the private endpoint. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.enableTelemetry` + +Optional. Enable/Disable usage telemetry for module. + +- Required: No +- Type: bool + +### Parameter: `privateEndpoints.ipConfigurations` + +Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints. + +- Required: No +- Type: array + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`groupId`](#parameter-privateendpointsipconfigurationsgroupid) | Yes | string | | +| [`memberName`](#parameter-privateendpointsipconfigurationsmembername) | Yes | string | | +| [`name`](#parameter-privateendpointsipconfigurationsname) | Yes | string | | +| [`privateIpAddress`](#parameter-privateendpointsipconfigurationsprivateipaddress) | Yes | string | | + +### Parameter: `privateEndpoints.ipConfigurations.groupId` +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.ipConfigurations.memberName` +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.ipConfigurations.name` +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.ipConfigurations.privateIpAddress` +- Required: Yes +- Type: string + + +### Parameter: `privateEndpoints.location` + +Optional. The location to deploy the private endpoint to. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.lock` + +Optional. Specify the type of lock. + +- Required: No +- Type: object + +### Parameter: `privateEndpoints.manualPrivateLinkServiceConnections` + +Optional. Manual PrivateLink Service Connections. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.name` + +Optional. The name of the private endpoint. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.privateDnsZoneGroupName` + +Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.privateDnsZoneResourceIds` + +Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.roleAssignments` + +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'. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.service` + +Optional. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob". + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.subnetResourceId` + +Required. Resource ID of the subnet where the endpoint needs to be created. + +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.tags` + +Optional. Tags to be applied on all resources/resource groups in this deployment. + +- Required: No +- Type: object ### Parameter: `roleAssignments` diff --git a/modules/insights/private-link-scope/main.bicep b/modules/insights/private-link-scope/main.bicep index fc8e46f04a..5d9aa08e33 100644 --- a/modules/insights/private-link-scope/main.bicep +++ b/modules/insights/private-link-scope/main.bicep @@ -19,7 +19,7 @@ param roleAssignments roleAssignmentType param scopedResources array = [] @description('Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible.') -param privateEndpoints array = [] +param privateEndpoints privateEndpointType @description('Optional. Resource tags.') param tags object = {} @@ -75,27 +75,27 @@ resource privateLinkScope_lock 'Microsoft.Authorization/locks@2020-05-01' = if ( scope: privateLinkScope } -module privateLinkScope_privateEndpoints '../../network/private-endpoint/main.bicep' = [for (privateEndpoint, index) in privateEndpoints: { - name: '${uniqueString(deployment().name, location)}-PvtLinkScope-PrivateEndpoint-${index}' +module privateLinkScope_privateEndpoints '../../network/private-endpoint/main.bicep' = [for (privateEndpoint, index) in (privateEndpoints ?? []): { + name: '${uniqueString(deployment().name, location)}-privateLinkScope-PrivateEndpoint-${index}' params: { groupIds: [ - privateEndpoint.service + privateEndpoint.?service ?? 'azuremonitor' ] - name: contains(privateEndpoint, 'name') ? privateEndpoint.name : 'pe-${last(split(privateLinkScope.id, '/'))}-${privateEndpoint.service}-${index}' + name: privateEndpoint.?name ?? 'pep-${last(split(privateLinkScope.id, '/'))}-${privateEndpoint.?service ?? 'azuremonitor'}-${index}' serviceResourceId: privateLinkScope.id subnetResourceId: privateEndpoint.subnetResourceId - enableDefaultTelemetry: enableReferencedModulesTelemetry - location: contains(privateEndpoint, 'location') ? privateEndpoint.location : reference(split(privateEndpoint.subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location + enableDefaultTelemetry: privateEndpoint.?enableDefaultTelemetry ?? enableReferencedModulesTelemetry + location: privateEndpoint.?location ?? reference(split(privateEndpoint.subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location lock: privateEndpoint.?lock ?? lock - privateDnsZoneGroupName: contains(privateEndpoint, 'privateDnsZoneGroupName') ? privateEndpoint.privateDnsZoneGroupName : 'default' - privateDnsZoneResourceIds: contains(privateEndpoint, 'privateDnsZoneResourceIds') ? privateEndpoint.privateDnsZoneResourceIds : [] - 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 : [] - applicationSecurityGroupResourceIds: contains(privateEndpoint, 'applicationSecurityGroupResourceIds') ? privateEndpoint.applicationSecurityGroupResourceIds : [] - customNetworkInterfaceName: contains(privateEndpoint, 'customNetworkInterfaceName') ? privateEndpoint.customNetworkInterfaceName : '' + privateDnsZoneGroupName: privateEndpoint.?privateDnsZoneGroupName + privateDnsZoneResourceIds: privateEndpoint.?privateDnsZoneResourceIds + roleAssignments: privateEndpoint.?roleAssignments + tags: privateEndpoint.?tags ?? tags + manualPrivateLinkServiceConnections: privateEndpoint.?manualPrivateLinkServiceConnections + customDnsConfigs: privateEndpoint.?customDnsConfigs + ipConfigurations: privateEndpoint.?ipConfigurations + applicationSecurityGroupResourceIds: privateEndpoint.?applicationSecurityGroupResourceIds + customNetworkInterfaceName: privateEndpoint.?customNetworkInterfaceName } }] @@ -159,3 +159,58 @@ type roleAssignmentType = { @description('Optional. The Resource Id of the delegated managed identity resource.') delegatedManagedIdentityResourceId: string? }[]? + +type privateEndpointType = { + @description('Optional. The name of the private endpoint.') + name: string? + + @description('Optional. The location to deploy the private endpoint to.') + location: string? + + @description('Optional. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob".') + service: string? + + @description('Required. Resource ID of the subnet where the endpoint needs to be created.') + subnetResourceId: string + + @description('Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided.') + privateDnsZoneGroupName: string? + + @description('Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones.') + privateDnsZoneResourceIds: string[]? + + @description('Optional. Custom DNS configurations.') + customDnsConfigs: { + fqdn: string? + ipAddresses: string[] + }[]? + + @description('Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints.') + ipConfigurations: { + name: string + groupId: string + memberName: string + privateIpAddress: string + }[]? + + @description('Optional. Application security groups in which the private endpoint IP configuration is included.') + applicationSecurityGroupResourceIds: string[]? + + @description('Optional. The custom name of the network interface attached to the private endpoint.') + customNetworkInterfaceName: string? + + @description('Optional. Specify the type of lock.') + lock: lockType + + @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\'.') + roleAssignments: roleAssignmentType + + @description('Optional. Tags to be applied on all resources/resource groups in this deployment.') + tags: object? + + @description('Optional. Manual PrivateLink Service Connections.') + manualPrivateLinkServiceConnections: array? + + @description('Optional. Enable/Disable usage telemetry for module.') + enableTelemetry: bool? +}[]? diff --git a/modules/insights/private-link-scope/main.json b/modules/insights/private-link-scope/main.json index 0e402b915a..f2fa8337c2 100644 --- a/modules/insights/private-link-scope/main.json +++ b/modules/insights/private-link-scope/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.22.6.54827", - "templateHash": "10019971976836793472" + "templateHash": "9175020405944005574" }, "name": "Azure Monitor Private Link Scopes", "description": "This module deploys an Azure Monitor Private Link Scope.", @@ -103,6 +103,155 @@ } }, "nullable": true + }, + "privateEndpointType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private endpoint." + } + }, + "location": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The location to deploy the private endpoint to." + } + }, + "service": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The service (sub-) type to deploy the private endpoint for. For example \"vault\" or \"blob\"." + } + }, + "subnetResourceId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the subnet where the endpoint needs to be created." + } + }, + "privateDnsZoneGroupName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided." + } + }, + "privateDnsZoneResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones." + } + }, + "customDnsConfigs": { + "type": "array", + "items": { + "type": "object", + "properties": { + "fqdn": { + "type": "string", + "nullable": true + }, + "ipAddresses": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. Custom DNS configurations." + } + }, + "ipConfigurations": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "groupId": { + "type": "string" + }, + "memberName": { + "type": "string" + }, + "privateIpAddress": { + "type": "string" + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints." + } + }, + "applicationSecurityGroupResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. Application security groups in which the private endpoint IP configuration is included." + } + }, + "customNetworkInterfaceName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The custom name of the network interface attached to the private endpoint." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "metadata": { + "description": "Optional. Specify the type of lock." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "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'." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags to be applied on all resources/resource groups in this deployment." + } + }, + "manualPrivateLinkServiceConnections": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. Manual PrivateLink Service Connections." + } + }, + "enableTelemetry": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + } + } + }, + "nullable": true } }, "parameters": { @@ -140,8 +289,7 @@ } }, "privateEndpoints": { - "type": "array", - "defaultValue": [], + "$ref": "#/definitions/privateEndpointType", "metadata": { "description": "Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible." } @@ -355,11 +503,11 @@ "privateLinkScope_privateEndpoints": { "copy": { "name": "privateLinkScope_privateEndpoints", - "count": "[length(parameters('privateEndpoints'))]" + "count": "[length(coalesce(parameters('privateEndpoints'), createArray()))]" }, "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", - "name": "[format('{0}-PvtLinkScope-PrivateEndpoint-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "name": "[format('{0}-privateLinkScope-PrivateEndpoint-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", "properties": { "expressionEvaluationOptions": { "scope": "inner" @@ -368,32 +516,54 @@ "parameters": { "groupIds": { "value": [ - "[parameters('privateEndpoints')[copyIndex()].service]" + "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'azuremonitor')]" ] }, - "name": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'name'), createObject('value', parameters('privateEndpoints')[copyIndex()].name), createObject('value', format('pe-{0}-{1}-{2}', last(split(resourceId('microsoft.insights/privateLinkScopes', parameters('name')), '/')), parameters('privateEndpoints')[copyIndex()].service, copyIndex())))]", + "name": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'name'), format('pep-{0}-{1}-{2}', last(split(resourceId('microsoft.insights/privateLinkScopes', parameters('name')), '/')), coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'azuremonitor'), copyIndex()))]" + }, "serviceResourceId": { "value": "[resourceId('microsoft.insights/privateLinkScopes', parameters('name'))]" }, "subnetResourceId": { - "value": "[parameters('privateEndpoints')[copyIndex()].subnetResourceId]" + "value": "[coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId]" }, "enableDefaultTelemetry": { - "value": "[variables('enableReferencedModulesTelemetry')]" + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'enableDefaultTelemetry'), variables('enableReferencedModulesTelemetry'))]" + }, + "location": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'location'), reference(split(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location)]" }, - "location": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'location'), createObject('value', parameters('privateEndpoints')[copyIndex()].location), createObject('value', reference(split(parameters('privateEndpoints')[copyIndex()].subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location))]", "lock": { - "value": "[coalesce(tryGet(parameters('privateEndpoints')[copyIndex()], 'lock'), parameters('lock'))]" - }, - "privateDnsZoneGroupName": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'privateDnsZoneGroupName'), createObject('value', parameters('privateEndpoints')[copyIndex()].privateDnsZoneGroupName), createObject('value', 'default'))]", - "privateDnsZoneResourceIds": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'privateDnsZoneResourceIds'), createObject('value', parameters('privateEndpoints')[copyIndex()].privateDnsZoneResourceIds), createObject('value', createArray()))]", - "roleAssignments": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'roleAssignments'), createObject('value', parameters('privateEndpoints')[copyIndex()].roleAssignments), createObject('value', createArray()))]", - "tags": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'tags'), createObject('value', parameters('privateEndpoints')[copyIndex()].tags), createObject('value', createObject()))]", - "manualPrivateLinkServiceConnections": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'manualPrivateLinkServiceConnections'), createObject('value', parameters('privateEndpoints')[copyIndex()].manualPrivateLinkServiceConnections), createObject('value', createArray()))]", - "customDnsConfigs": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'customDnsConfigs'), createObject('value', parameters('privateEndpoints')[copyIndex()].customDnsConfigs), createObject('value', createArray()))]", - "ipConfigurations": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'ipConfigurations'), createObject('value', parameters('privateEndpoints')[copyIndex()].ipConfigurations), createObject('value', createArray()))]", - "applicationSecurityGroupResourceIds": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'applicationSecurityGroupResourceIds'), createObject('value', parameters('privateEndpoints')[copyIndex()].applicationSecurityGroupResourceIds), createObject('value', createArray()))]", - "customNetworkInterfaceName": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'customNetworkInterfaceName'), createObject('value', parameters('privateEndpoints')[copyIndex()].customNetworkInterfaceName), createObject('value', ''))]" + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'lock'), parameters('lock'))]" + }, + "privateDnsZoneGroupName": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneGroupName')]" + }, + "privateDnsZoneResourceIds": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneResourceIds')]" + }, + "roleAssignments": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'roleAssignments')]" + }, + "tags": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'tags'), parameters('tags'))]" + }, + "manualPrivateLinkServiceConnections": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'manualPrivateLinkServiceConnections')]" + }, + "customDnsConfigs": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customDnsConfigs')]" + }, + "ipConfigurations": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'ipConfigurations')]" + }, + "applicationSecurityGroupResourceIds": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'applicationSecurityGroupResourceIds')]" + }, + "customNetworkInterfaceName": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customNetworkInterfaceName')]" + } }, "template": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", diff --git a/modules/key-vault/vault/README.md b/modules/key-vault/vault/README.md index 81cb74a612..33bab3b6b6 100644 --- a/modules/key-vault/vault/README.md +++ b/modules/key-vault/vault/README.md @@ -911,7 +911,168 @@ Service endpoint object information. For security reasons, it is recommended to Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible. - Required: No - Type: array -- Default: `[]` + + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`applicationSecurityGroupResourceIds`](#parameter-privateendpointsapplicationsecuritygroupresourceids) | No | array | Optional. Application security groups in which the private endpoint IP configuration is included. | +| [`customDnsConfigs`](#parameter-privateendpointscustomdnsconfigs) | No | array | Optional. Custom DNS configurations. | +| [`customNetworkInterfaceName`](#parameter-privateendpointscustomnetworkinterfacename) | No | string | Optional. The custom name of the network interface attached to the private endpoint. | +| [`enableTelemetry`](#parameter-privateendpointsenabletelemetry) | No | bool | Optional. Enable/Disable usage telemetry for module. | +| [`ipConfigurations`](#parameter-privateendpointsipconfigurations) | No | array | Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints. | +| [`location`](#parameter-privateendpointslocation) | No | string | Optional. The location to deploy the private endpoint to. | +| [`lock`](#parameter-privateendpointslock) | No | object | Optional. Specify the type of lock. | +| [`manualPrivateLinkServiceConnections`](#parameter-privateendpointsmanualprivatelinkserviceconnections) | No | array | Optional. Manual PrivateLink Service Connections. | +| [`name`](#parameter-privateendpointsname) | No | string | Optional. The name of the private endpoint. | +| [`privateDnsZoneGroupName`](#parameter-privateendpointsprivatednszonegroupname) | No | string | Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided. | +| [`privateDnsZoneResourceIds`](#parameter-privateendpointsprivatednszoneresourceids) | No | array | Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones. | +| [`roleAssignments`](#parameter-privateendpointsroleassignments) | No | array | 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'. | +| [`service`](#parameter-privateendpointsservice) | No | string | Optional. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob". | +| [`subnetResourceId`](#parameter-privateendpointssubnetresourceid) | Yes | string | Required. Resource ID of the subnet where the endpoint needs to be created. | +| [`tags`](#parameter-privateendpointstags) | No | object | Optional. Tags to be applied on all resources/resource groups in this deployment. | + +### Parameter: `privateEndpoints.applicationSecurityGroupResourceIds` + +Optional. Application security groups in which the private endpoint IP configuration is included. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.customDnsConfigs` + +Optional. Custom DNS configurations. + +- Required: No +- Type: array + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`fqdn`](#parameter-privateendpointscustomdnsconfigsfqdn) | No | string | | +| [`ipAddresses`](#parameter-privateendpointscustomdnsconfigsipaddresses) | Yes | array | | + +### Parameter: `privateEndpoints.customDnsConfigs.fqdn` +- Required: No +- Type: string + +### Parameter: `privateEndpoints.customDnsConfigs.ipAddresses` +- Required: Yes +- Type: array + + +### Parameter: `privateEndpoints.customNetworkInterfaceName` + +Optional. The custom name of the network interface attached to the private endpoint. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.enableTelemetry` + +Optional. Enable/Disable usage telemetry for module. + +- Required: No +- Type: bool + +### Parameter: `privateEndpoints.ipConfigurations` + +Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints. + +- Required: No +- Type: array + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`groupId`](#parameter-privateendpointsipconfigurationsgroupid) | Yes | string | | +| [`memberName`](#parameter-privateendpointsipconfigurationsmembername) | Yes | string | | +| [`name`](#parameter-privateendpointsipconfigurationsname) | Yes | string | | +| [`privateIpAddress`](#parameter-privateendpointsipconfigurationsprivateipaddress) | Yes | string | | + +### Parameter: `privateEndpoints.ipConfigurations.groupId` +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.ipConfigurations.memberName` +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.ipConfigurations.name` +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.ipConfigurations.privateIpAddress` +- Required: Yes +- Type: string + + +### Parameter: `privateEndpoints.location` + +Optional. The location to deploy the private endpoint to. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.lock` + +Optional. Specify the type of lock. + +- Required: No +- Type: object + +### Parameter: `privateEndpoints.manualPrivateLinkServiceConnections` + +Optional. Manual PrivateLink Service Connections. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.name` + +Optional. The name of the private endpoint. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.privateDnsZoneGroupName` + +Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.privateDnsZoneResourceIds` + +Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.roleAssignments` + +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'. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.service` + +Optional. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob". + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.subnetResourceId` + +Required. Resource ID of the subnet where the endpoint needs to be created. + +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.tags` + +Optional. Tags to be applied on all resources/resource groups in this deployment. + +- Required: No +- Type: object ### Parameter: `publicNetworkAccess` diff --git a/modules/key-vault/vault/main.bicep b/modules/key-vault/vault/main.bicep index 5977b4faf0..f66a490005 100644 --- a/modules/key-vault/vault/main.bicep +++ b/modules/key-vault/vault/main.bicep @@ -83,7 +83,7 @@ param lock lockType param roleAssignments roleAssignmentType @description('Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible.') -param privateEndpoints array = [] +param privateEndpoints privateEndpointType @description('Optional. Resource tags.') param tags object = {} @@ -272,27 +272,27 @@ module keyVault_keys 'key/main.bicep' = [for (key, index) in keys: { } }] -module keyVault_privateEndpoints '../../network/private-endpoint/main.bicep' = [for (privateEndpoint, index) in privateEndpoints: { - name: '${uniqueString(deployment().name, location)}-KeyVault-PrivateEndpoint-${index}' +module keyVault_privateEndpoints '../../network/private-endpoint/main.bicep' = [for (privateEndpoint, index) in (privateEndpoints ?? []): { + name: '${uniqueString(deployment().name, location)}-keyVault-PrivateEndpoint-${index}' params: { groupIds: [ - privateEndpoint.service + privateEndpoint.?service ?? 'vault' ] - name: contains(privateEndpoint, 'name') ? privateEndpoint.name : 'pe-${last(split(keyVault.id, '/'))}-${privateEndpoint.service}-${index}' + name: privateEndpoint.?name ?? 'pep-${last(split(keyVault.id, '/'))}-${privateEndpoint.?service ?? 'vault'}-${index}' serviceResourceId: keyVault.id subnetResourceId: privateEndpoint.subnetResourceId - enableDefaultTelemetry: enableReferencedModulesTelemetry - location: contains(privateEndpoint, 'location') ? privateEndpoint.location : reference(split(privateEndpoint.subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location + enableDefaultTelemetry: privateEndpoint.?enableDefaultTelemetry ?? enableReferencedModulesTelemetry + location: privateEndpoint.?location ?? reference(split(privateEndpoint.subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location lock: privateEndpoint.?lock ?? lock - privateDnsZoneGroupName: contains(privateEndpoint, 'privateDnsZoneGroupName') ? privateEndpoint.privateDnsZoneGroupName : 'default' - privateDnsZoneResourceIds: contains(privateEndpoint, 'privateDnsZoneResourceIds') ? privateEndpoint.privateDnsZoneResourceIds : [] - 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 : [] - applicationSecurityGroupResourceIds: contains(privateEndpoint, 'applicationSecurityGroupResourceIds') ? privateEndpoint.applicationSecurityGroupResourceIds : [] - customNetworkInterfaceName: contains(privateEndpoint, 'customNetworkInterfaceName') ? privateEndpoint.customNetworkInterfaceName : '' + privateDnsZoneGroupName: privateEndpoint.?privateDnsZoneGroupName + privateDnsZoneResourceIds: privateEndpoint.?privateDnsZoneResourceIds + roleAssignments: privateEndpoint.?roleAssignments + tags: privateEndpoint.?tags ?? tags + manualPrivateLinkServiceConnections: privateEndpoint.?manualPrivateLinkServiceConnections + customDnsConfigs: privateEndpoint.?customDnsConfigs + ipConfigurations: privateEndpoint.?ipConfigurations + applicationSecurityGroupResourceIds: privateEndpoint.?applicationSecurityGroupResourceIds + customNetworkInterfaceName: privateEndpoint.?customNetworkInterfaceName } }] @@ -362,3 +362,58 @@ type roleAssignmentType = { @description('Optional. The Resource Id of the delegated managed identity resource.') delegatedManagedIdentityResourceId: string? }[]? + +type privateEndpointType = { + @description('Optional. The name of the private endpoint.') + name: string? + + @description('Optional. The location to deploy the private endpoint to.') + location: string? + + @description('Optional. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob".') + service: string? + + @description('Required. Resource ID of the subnet where the endpoint needs to be created.') + subnetResourceId: string + + @description('Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided.') + privateDnsZoneGroupName: string? + + @description('Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones.') + privateDnsZoneResourceIds: string[]? + + @description('Optional. Custom DNS configurations.') + customDnsConfigs: { + fqdn: string? + ipAddresses: string[] + }[]? + + @description('Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints.') + ipConfigurations: { + name: string + groupId: string + memberName: string + privateIpAddress: string + }[]? + + @description('Optional. Application security groups in which the private endpoint IP configuration is included.') + applicationSecurityGroupResourceIds: string[]? + + @description('Optional. The custom name of the network interface attached to the private endpoint.') + customNetworkInterfaceName: string? + + @description('Optional. Specify the type of lock.') + lock: lockType + + @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\'.') + roleAssignments: roleAssignmentType + + @description('Optional. Tags to be applied on all resources/resource groups in this deployment.') + tags: object? + + @description('Optional. Manual PrivateLink Service Connections.') + manualPrivateLinkServiceConnections: array? + + @description('Optional. Enable/Disable usage telemetry for module.') + enableTelemetry: bool? +}[]? diff --git a/modules/key-vault/vault/main.json b/modules/key-vault/vault/main.json index 3efab0881e..7b155172e1 100644 --- a/modules/key-vault/vault/main.json +++ b/modules/key-vault/vault/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.22.6.54827", - "templateHash": "2886634889186543886" + "templateHash": "502304386016256434" }, "name": "Key Vaults", "description": "This module deploys a Key Vault.", @@ -103,6 +103,155 @@ } }, "nullable": true + }, + "privateEndpointType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private endpoint." + } + }, + "location": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The location to deploy the private endpoint to." + } + }, + "service": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The service (sub-) type to deploy the private endpoint for. For example \"vault\" or \"blob\"." + } + }, + "subnetResourceId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the subnet where the endpoint needs to be created." + } + }, + "privateDnsZoneGroupName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided." + } + }, + "privateDnsZoneResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones." + } + }, + "customDnsConfigs": { + "type": "array", + "items": { + "type": "object", + "properties": { + "fqdn": { + "type": "string", + "nullable": true + }, + "ipAddresses": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. Custom DNS configurations." + } + }, + "ipConfigurations": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "groupId": { + "type": "string" + }, + "memberName": { + "type": "string" + }, + "privateIpAddress": { + "type": "string" + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints." + } + }, + "applicationSecurityGroupResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. Application security groups in which the private endpoint IP configuration is included." + } + }, + "customNetworkInterfaceName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The custom name of the network interface attached to the private endpoint." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "metadata": { + "description": "Optional. Specify the type of lock." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "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'." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags to be applied on all resources/resource groups in this deployment." + } + }, + "manualPrivateLinkServiceConnections": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. Manual PrivateLink Service Connections." + } + }, + "enableTelemetry": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + } + } + }, + "nullable": true } }, "parameters": { @@ -268,8 +417,7 @@ } }, "privateEndpoints": { - "type": "array", - "defaultValue": [], + "$ref": "#/definitions/privateEndpointType", "metadata": { "description": "Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible." } @@ -1224,11 +1372,11 @@ "keyVault_privateEndpoints": { "copy": { "name": "keyVault_privateEndpoints", - "count": "[length(parameters('privateEndpoints'))]" + "count": "[length(coalesce(parameters('privateEndpoints'), createArray()))]" }, "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", - "name": "[format('{0}-KeyVault-PrivateEndpoint-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "name": "[format('{0}-keyVault-PrivateEndpoint-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", "properties": { "expressionEvaluationOptions": { "scope": "inner" @@ -1237,32 +1385,54 @@ "parameters": { "groupIds": { "value": [ - "[parameters('privateEndpoints')[copyIndex()].service]" + "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'vault')]" ] }, - "name": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'name'), createObject('value', parameters('privateEndpoints')[copyIndex()].name), createObject('value', format('pe-{0}-{1}-{2}', last(split(resourceId('Microsoft.KeyVault/vaults', parameters('name')), '/')), parameters('privateEndpoints')[copyIndex()].service, copyIndex())))]", + "name": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'name'), format('pep-{0}-{1}-{2}', last(split(resourceId('Microsoft.KeyVault/vaults', parameters('name')), '/')), coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'vault'), copyIndex()))]" + }, "serviceResourceId": { "value": "[resourceId('Microsoft.KeyVault/vaults', parameters('name'))]" }, "subnetResourceId": { - "value": "[parameters('privateEndpoints')[copyIndex()].subnetResourceId]" + "value": "[coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId]" }, "enableDefaultTelemetry": { - "value": "[variables('enableReferencedModulesTelemetry')]" + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'enableDefaultTelemetry'), variables('enableReferencedModulesTelemetry'))]" + }, + "location": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'location'), reference(split(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location)]" }, - "location": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'location'), createObject('value', parameters('privateEndpoints')[copyIndex()].location), createObject('value', reference(split(parameters('privateEndpoints')[copyIndex()].subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location))]", "lock": { - "value": "[coalesce(tryGet(parameters('privateEndpoints')[copyIndex()], 'lock'), parameters('lock'))]" + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'lock'), parameters('lock'))]" + }, + "privateDnsZoneGroupName": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneGroupName')]" + }, + "privateDnsZoneResourceIds": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneResourceIds')]" + }, + "roleAssignments": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'roleAssignments')]" }, - "privateDnsZoneGroupName": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'privateDnsZoneGroupName'), createObject('value', parameters('privateEndpoints')[copyIndex()].privateDnsZoneGroupName), createObject('value', 'default'))]", - "privateDnsZoneResourceIds": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'privateDnsZoneResourceIds'), createObject('value', parameters('privateEndpoints')[copyIndex()].privateDnsZoneResourceIds), createObject('value', createArray()))]", - "roleAssignments": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'roleAssignments'), createObject('value', parameters('privateEndpoints')[copyIndex()].roleAssignments), createObject('value', createArray()))]", - "tags": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'tags'), createObject('value', parameters('privateEndpoints')[copyIndex()].tags), createObject('value', createObject()))]", - "manualPrivateLinkServiceConnections": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'manualPrivateLinkServiceConnections'), createObject('value', parameters('privateEndpoints')[copyIndex()].manualPrivateLinkServiceConnections), createObject('value', createArray()))]", - "customDnsConfigs": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'customDnsConfigs'), createObject('value', parameters('privateEndpoints')[copyIndex()].customDnsConfigs), createObject('value', createArray()))]", - "ipConfigurations": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'ipConfigurations'), createObject('value', parameters('privateEndpoints')[copyIndex()].ipConfigurations), createObject('value', createArray()))]", - "applicationSecurityGroupResourceIds": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'applicationSecurityGroupResourceIds'), createObject('value', parameters('privateEndpoints')[copyIndex()].applicationSecurityGroupResourceIds), createObject('value', createArray()))]", - "customNetworkInterfaceName": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'customNetworkInterfaceName'), createObject('value', parameters('privateEndpoints')[copyIndex()].customNetworkInterfaceName), createObject('value', ''))]" + "tags": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'tags'), parameters('tags'))]" + }, + "manualPrivateLinkServiceConnections": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'manualPrivateLinkServiceConnections')]" + }, + "customDnsConfigs": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customDnsConfigs')]" + }, + "ipConfigurations": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'ipConfigurations')]" + }, + "applicationSecurityGroupResourceIds": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'applicationSecurityGroupResourceIds')]" + }, + "customNetworkInterfaceName": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customNetworkInterfaceName')]" + } }, "template": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", diff --git a/modules/machine-learning-services/workspace/.test/common/main.test.bicep b/modules/machine-learning-services/workspace/.test/common/main.test.bicep index c1353d2cb1..2879c22fbb 100644 --- a/modules/machine-learning-services/workspace/.test/common/main.test.bicep +++ b/modules/machine-learning-services/workspace/.test/common/main.test.bicep @@ -117,7 +117,6 @@ module testDeployment '../../main.bicep' = { primaryUserAssignedIdentity: nestedDependencies.outputs.managedIdentityResourceId privateEndpoints: [ { - service: 'amlworkspace' subnetResourceId: nestedDependencies.outputs.subnetResourceId privateDnsZoneResourceIds: [ nestedDependencies.outputs.privateDNSZoneResourceId diff --git a/modules/machine-learning-services/workspace/README.md b/modules/machine-learning-services/workspace/README.md index a631dfac05..9626b84295 100644 --- a/modules/machine-learning-services/workspace/README.md +++ b/modules/machine-learning-services/workspace/README.md @@ -101,7 +101,6 @@ module workspace 'br:bicep/modules/machine-learning-services.workspace:1.0.0' = privateDnsZoneResourceIds: [ '' ] - service: 'amlworkspace' subnetResourceId: '' tags: { Environment: 'Non-Prod' @@ -228,7 +227,6 @@ module workspace 'br:bicep/modules/machine-learning-services.workspace:1.0.0' = "privateDnsZoneResourceIds": [ "" ], - "service": "amlworkspace", "subnetResourceId": "", "tags": { "Environment": "Non-Prod", @@ -726,7 +724,168 @@ The user assigned identity resource ID that represents the workspace identity. R Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible. - Required: No - Type: array -- Default: `[]` + + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`applicationSecurityGroupResourceIds`](#parameter-privateendpointsapplicationsecuritygroupresourceids) | No | array | Optional. Application security groups in which the private endpoint IP configuration is included. | +| [`customDnsConfigs`](#parameter-privateendpointscustomdnsconfigs) | No | array | Optional. Custom DNS configurations. | +| [`customNetworkInterfaceName`](#parameter-privateendpointscustomnetworkinterfacename) | No | string | Optional. The custom name of the network interface attached to the private endpoint. | +| [`enableTelemetry`](#parameter-privateendpointsenabletelemetry) | No | bool | Optional. Enable/Disable usage telemetry for module. | +| [`ipConfigurations`](#parameter-privateendpointsipconfigurations) | No | array | Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints. | +| [`location`](#parameter-privateendpointslocation) | No | string | Optional. The location to deploy the private endpoint to. | +| [`lock`](#parameter-privateendpointslock) | No | object | Optional. Specify the type of lock. | +| [`manualPrivateLinkServiceConnections`](#parameter-privateendpointsmanualprivatelinkserviceconnections) | No | array | Optional. Manual PrivateLink Service Connections. | +| [`name`](#parameter-privateendpointsname) | No | string | Optional. The name of the private endpoint. | +| [`privateDnsZoneGroupName`](#parameter-privateendpointsprivatednszonegroupname) | No | string | Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided. | +| [`privateDnsZoneResourceIds`](#parameter-privateendpointsprivatednszoneresourceids) | No | array | Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones. | +| [`roleAssignments`](#parameter-privateendpointsroleassignments) | No | array | 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'. | +| [`service`](#parameter-privateendpointsservice) | No | string | Optional. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob". | +| [`subnetResourceId`](#parameter-privateendpointssubnetresourceid) | Yes | string | Required. Resource ID of the subnet where the endpoint needs to be created. | +| [`tags`](#parameter-privateendpointstags) | No | object | Optional. Tags to be applied on all resources/resource groups in this deployment. | + +### Parameter: `privateEndpoints.applicationSecurityGroupResourceIds` + +Optional. Application security groups in which the private endpoint IP configuration is included. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.customDnsConfigs` + +Optional. Custom DNS configurations. + +- Required: No +- Type: array + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`fqdn`](#parameter-privateendpointscustomdnsconfigsfqdn) | No | string | | +| [`ipAddresses`](#parameter-privateendpointscustomdnsconfigsipaddresses) | Yes | array | | + +### Parameter: `privateEndpoints.customDnsConfigs.fqdn` +- Required: No +- Type: string + +### Parameter: `privateEndpoints.customDnsConfigs.ipAddresses` +- Required: Yes +- Type: array + + +### Parameter: `privateEndpoints.customNetworkInterfaceName` + +Optional. The custom name of the network interface attached to the private endpoint. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.enableTelemetry` + +Optional. Enable/Disable usage telemetry for module. + +- Required: No +- Type: bool + +### Parameter: `privateEndpoints.ipConfigurations` + +Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints. + +- Required: No +- Type: array + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`groupId`](#parameter-privateendpointsipconfigurationsgroupid) | Yes | string | | +| [`memberName`](#parameter-privateendpointsipconfigurationsmembername) | Yes | string | | +| [`name`](#parameter-privateendpointsipconfigurationsname) | Yes | string | | +| [`privateIpAddress`](#parameter-privateendpointsipconfigurationsprivateipaddress) | Yes | string | | + +### Parameter: `privateEndpoints.ipConfigurations.groupId` +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.ipConfigurations.memberName` +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.ipConfigurations.name` +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.ipConfigurations.privateIpAddress` +- Required: Yes +- Type: string + + +### Parameter: `privateEndpoints.location` + +Optional. The location to deploy the private endpoint to. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.lock` + +Optional. Specify the type of lock. + +- Required: No +- Type: object + +### Parameter: `privateEndpoints.manualPrivateLinkServiceConnections` + +Optional. Manual PrivateLink Service Connections. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.name` + +Optional. The name of the private endpoint. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.privateDnsZoneGroupName` + +Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.privateDnsZoneResourceIds` + +Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.roleAssignments` + +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'. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.service` + +Optional. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob". + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.subnetResourceId` + +Required. Resource ID of the subnet where the endpoint needs to be created. + +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.tags` + +Optional. Tags to be applied on all resources/resource groups in this deployment. + +- Required: No +- Type: object ### Parameter: `publicNetworkAccess` diff --git a/modules/machine-learning-services/workspace/main.bicep b/modules/machine-learning-services/workspace/main.bicep index ca840eaec5..28c115e28f 100644 --- a/modules/machine-learning-services/workspace/main.bicep +++ b/modules/machine-learning-services/workspace/main.bicep @@ -45,7 +45,7 @@ param allowPublicAccessWhenBehindVnet bool = false param roleAssignments roleAssignmentType @sys.description('Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible.') -param privateEndpoints array = [] +param privateEndpoints privateEndpointType @sys.description('Optional. Computes to create respectively attach to the workspace.') param computes array = [] @@ -287,24 +287,27 @@ resource workspace_diagnosticSettings 'Microsoft.Insights/diagnosticsettings@202 scope: workspace } -module workspace_privateEndpoints '../../network/private-endpoint/main.bicep' = [for (privateEndpoint, index) in privateEndpoints: { - name: '${uniqueString(deployment().name, location)}-Workspace-PrivateEndpoint-${index}' +module workspace_privateEndpoints '../../network/private-endpoint/main.bicep' = [for (privateEndpoint, index) in (privateEndpoints ?? []): { + name: '${uniqueString(deployment().name, location)}-workspace-PrivateEndpoint-${index}' params: { groupIds: [ - privateEndpoint.service + privateEndpoint.?service ?? 'amlworkspace' ] - name: contains(privateEndpoint, 'name') ? privateEndpoint.name : 'pe-${last(split(workspace.id, '/'))}-${privateEndpoint.service}-${index}' + name: privateEndpoint.?name ?? 'pep-${last(split(workspace.id, '/'))}-${privateEndpoint.?service ?? 'amlworkspace'}-${index}' serviceResourceId: workspace.id subnetResourceId: privateEndpoint.subnetResourceId - enableDefaultTelemetry: enableReferencedModulesTelemetry - location: contains(privateEndpoint, 'location') ? privateEndpoint.location : reference(split(privateEndpoint.subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location + enableDefaultTelemetry: privateEndpoint.?enableDefaultTelemetry ?? enableReferencedModulesTelemetry + location: privateEndpoint.?location ?? reference(split(privateEndpoint.subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location lock: privateEndpoint.?lock ?? lock - privateDnsZoneGroupName: contains(privateEndpoint, 'privateDnsZoneGroupName') ? privateEndpoint.privateDnsZoneGroupName : 'default' - privateDnsZoneResourceIds: contains(privateEndpoint, 'privateDnsZoneResourceIds') ? privateEndpoint.privateDnsZoneResourceIds : [] - roleAssignments: contains(privateEndpoint, 'roleAssignments') ? privateEndpoint.roleAssignments : [] - tags: contains(privateEndpoint, 'tags') ? privateEndpoint.tags : {} - manualPrivateLinkServiceConnections: contains(privateEndpoint, 'manualPrivateLinkServiceConnections') ? privateEndpoint.manualPrivateLinkServiceConnections : [] - customDnsConfigs: contains(privateEndpoint, 'customDnsConfigs') ? privateEndpoint.customDnsConfigs : [] + privateDnsZoneGroupName: privateEndpoint.?privateDnsZoneGroupName + privateDnsZoneResourceIds: privateEndpoint.?privateDnsZoneResourceIds + roleAssignments: privateEndpoint.?roleAssignments + tags: privateEndpoint.?tags ?? tags + manualPrivateLinkServiceConnections: privateEndpoint.?manualPrivateLinkServiceConnections + customDnsConfigs: privateEndpoint.?customDnsConfigs + ipConfigurations: privateEndpoint.?ipConfigurations + applicationSecurityGroupResourceIds: privateEndpoint.?applicationSecurityGroupResourceIds + customNetworkInterfaceName: privateEndpoint.?customNetworkInterfaceName } }] @@ -375,3 +378,58 @@ type roleAssignmentType = { @sys.description('Optional. The Resource Id of the delegated managed identity resource.') delegatedManagedIdentityResourceId: string? }[]? + +type privateEndpointType = { + @sys.description('Optional. The name of the private endpoint.') + name: string? + + @sys.description('Optional. The location to deploy the private endpoint to.') + location: string? + + @sys.description('Optional. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob".') + service: string? + + @sys.description('Required. Resource ID of the subnet where the endpoint needs to be created.') + subnetResourceId: string + + @sys.description('Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided.') + privateDnsZoneGroupName: string? + + @sys.description('Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones.') + privateDnsZoneResourceIds: string[]? + + @sys.description('Optional. Custom DNS configurations.') + customDnsConfigs: { + fqdn: string? + ipAddresses: string[] + }[]? + + @sys.description('Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints.') + ipConfigurations: { + name: string + groupId: string + memberName: string + privateIpAddress: string + }[]? + + @sys.description('Optional. Application security groups in which the private endpoint IP configuration is included.') + applicationSecurityGroupResourceIds: string[]? + + @sys.description('Optional. The custom name of the network interface attached to the private endpoint.') + customNetworkInterfaceName: string? + + @sys.description('Optional. Specify the type of lock.') + lock: lockType + + @sys.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\'.') + roleAssignments: roleAssignmentType + + @sys.description('Optional. Tags to be applied on all resources/resource groups in this deployment.') + tags: object? + + @sys.description('Optional. Manual PrivateLink Service Connections.') + manualPrivateLinkServiceConnections: array? + + @sys.description('Optional. Enable/Disable usage telemetry for module.') + enableTelemetry: bool? +}[]? diff --git a/modules/machine-learning-services/workspace/main.json b/modules/machine-learning-services/workspace/main.json index afeeb96c69..ff015569ab 100644 --- a/modules/machine-learning-services/workspace/main.json +++ b/modules/machine-learning-services/workspace/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.22.6.54827", - "templateHash": "16042425062775405859" + "templateHash": "16867204507762880761" }, "name": "Machine Learning Services Workspaces", "description": "This module deploys a Machine Learning Services Workspace.", @@ -103,6 +103,155 @@ } }, "nullable": true + }, + "privateEndpointType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private endpoint." + } + }, + "location": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The location to deploy the private endpoint to." + } + }, + "service": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The service (sub-) type to deploy the private endpoint for. For example \"vault\" or \"blob\"." + } + }, + "subnetResourceId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the subnet where the endpoint needs to be created." + } + }, + "privateDnsZoneGroupName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided." + } + }, + "privateDnsZoneResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones." + } + }, + "customDnsConfigs": { + "type": "array", + "items": { + "type": "object", + "properties": { + "fqdn": { + "type": "string", + "nullable": true + }, + "ipAddresses": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. Custom DNS configurations." + } + }, + "ipConfigurations": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "groupId": { + "type": "string" + }, + "memberName": { + "type": "string" + }, + "privateIpAddress": { + "type": "string" + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints." + } + }, + "applicationSecurityGroupResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. Application security groups in which the private endpoint IP configuration is included." + } + }, + "customNetworkInterfaceName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The custom name of the network interface attached to the private endpoint." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "metadata": { + "description": "Optional. Specify the type of lock." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "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'." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags to be applied on all resources/resource groups in this deployment." + } + }, + "manualPrivateLinkServiceConnections": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. Manual PrivateLink Service Connections." + } + }, + "enableTelemetry": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + } + } + }, + "nullable": true } }, "parameters": { @@ -183,8 +332,7 @@ } }, "privateEndpoints": { - "type": "array", - "defaultValue": [], + "$ref": "#/definitions/privateEndpointType", "metadata": { "description": "Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible." } @@ -785,11 +933,11 @@ "workspace_privateEndpoints": { "copy": { "name": "workspace_privateEndpoints", - "count": "[length(parameters('privateEndpoints'))]" + "count": "[length(coalesce(parameters('privateEndpoints'), createArray()))]" }, "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", - "name": "[format('{0}-Workspace-PrivateEndpoint-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "name": "[format('{0}-workspace-PrivateEndpoint-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", "properties": { "expressionEvaluationOptions": { "scope": "inner" @@ -798,29 +946,54 @@ "parameters": { "groupIds": { "value": [ - "[parameters('privateEndpoints')[copyIndex()].service]" + "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'amlworkspace')]" ] }, - "name": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'name'), createObject('value', parameters('privateEndpoints')[copyIndex()].name), createObject('value', format('pe-{0}-{1}-{2}', last(split(resourceId('Microsoft.MachineLearningServices/workspaces', parameters('name')), '/')), parameters('privateEndpoints')[copyIndex()].service, copyIndex())))]", + "name": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'name'), format('pep-{0}-{1}-{2}', last(split(resourceId('Microsoft.MachineLearningServices/workspaces', parameters('name')), '/')), coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'amlworkspace'), copyIndex()))]" + }, "serviceResourceId": { "value": "[resourceId('Microsoft.MachineLearningServices/workspaces', parameters('name'))]" }, "subnetResourceId": { - "value": "[parameters('privateEndpoints')[copyIndex()].subnetResourceId]" + "value": "[coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId]" }, "enableDefaultTelemetry": { - "value": "[variables('enableReferencedModulesTelemetry')]" + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'enableDefaultTelemetry'), variables('enableReferencedModulesTelemetry'))]" + }, + "location": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'location'), reference(split(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location)]" }, - "location": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'location'), createObject('value', parameters('privateEndpoints')[copyIndex()].location), createObject('value', reference(split(parameters('privateEndpoints')[copyIndex()].subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location))]", "lock": { - "value": "[coalesce(tryGet(parameters('privateEndpoints')[copyIndex()], 'lock'), parameters('lock'))]" + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'lock'), parameters('lock'))]" + }, + "privateDnsZoneGroupName": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneGroupName')]" + }, + "privateDnsZoneResourceIds": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneResourceIds')]" + }, + "roleAssignments": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'roleAssignments')]" }, - "privateDnsZoneGroupName": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'privateDnsZoneGroupName'), createObject('value', parameters('privateEndpoints')[copyIndex()].privateDnsZoneGroupName), createObject('value', 'default'))]", - "privateDnsZoneResourceIds": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'privateDnsZoneResourceIds'), createObject('value', parameters('privateEndpoints')[copyIndex()].privateDnsZoneResourceIds), createObject('value', createArray()))]", - "roleAssignments": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'roleAssignments'), createObject('value', parameters('privateEndpoints')[copyIndex()].roleAssignments), createObject('value', createArray()))]", - "tags": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'tags'), createObject('value', parameters('privateEndpoints')[copyIndex()].tags), createObject('value', createObject()))]", - "manualPrivateLinkServiceConnections": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'manualPrivateLinkServiceConnections'), createObject('value', parameters('privateEndpoints')[copyIndex()].manualPrivateLinkServiceConnections), createObject('value', createArray()))]", - "customDnsConfigs": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'customDnsConfigs'), createObject('value', parameters('privateEndpoints')[copyIndex()].customDnsConfigs), createObject('value', createArray()))]" + "tags": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'tags'), parameters('tags'))]" + }, + "manualPrivateLinkServiceConnections": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'manualPrivateLinkServiceConnections')]" + }, + "customDnsConfigs": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customDnsConfigs')]" + }, + "ipConfigurations": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'ipConfigurations')]" + }, + "applicationSecurityGroupResourceIds": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'applicationSecurityGroupResourceIds')]" + }, + "customNetworkInterfaceName": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customNetworkInterfaceName')]" + } }, "template": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", diff --git a/modules/network/application-gateway/README.md b/modules/network/application-gateway/README.md index f1c4883c3f..c9d9112588 100644 --- a/modules/network/application-gateway/README.md +++ b/modules/network/application-gateway/README.md @@ -1248,7 +1248,168 @@ Name of the Application Gateway. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible. - Required: No - Type: array -- Default: `[]` + + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`applicationSecurityGroupResourceIds`](#parameter-privateendpointsapplicationsecuritygroupresourceids) | No | array | Optional. Application security groups in which the private endpoint IP configuration is included. | +| [`customDnsConfigs`](#parameter-privateendpointscustomdnsconfigs) | No | array | Optional. Custom DNS configurations. | +| [`customNetworkInterfaceName`](#parameter-privateendpointscustomnetworkinterfacename) | No | string | Optional. The custom name of the network interface attached to the private endpoint. | +| [`enableTelemetry`](#parameter-privateendpointsenabletelemetry) | No | bool | Optional. Enable/Disable usage telemetry for module. | +| [`ipConfigurations`](#parameter-privateendpointsipconfigurations) | No | array | Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints. | +| [`location`](#parameter-privateendpointslocation) | No | string | Optional. The location to deploy the private endpoint to. | +| [`lock`](#parameter-privateendpointslock) | No | object | Optional. Specify the type of lock. | +| [`manualPrivateLinkServiceConnections`](#parameter-privateendpointsmanualprivatelinkserviceconnections) | No | array | Optional. Manual PrivateLink Service Connections. | +| [`name`](#parameter-privateendpointsname) | No | string | Optional. The name of the private endpoint. | +| [`privateDnsZoneGroupName`](#parameter-privateendpointsprivatednszonegroupname) | No | string | Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided. | +| [`privateDnsZoneResourceIds`](#parameter-privateendpointsprivatednszoneresourceids) | No | array | Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones. | +| [`roleAssignments`](#parameter-privateendpointsroleassignments) | No | array | 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'. | +| [`service`](#parameter-privateendpointsservice) | Yes | string | Required. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob". | +| [`subnetResourceId`](#parameter-privateendpointssubnetresourceid) | Yes | string | Required. Resource ID of the subnet where the endpoint needs to be created. | +| [`tags`](#parameter-privateendpointstags) | No | object | Optional. Tags to be applied on all resources/resource groups in this deployment. | + +### Parameter: `privateEndpoints.applicationSecurityGroupResourceIds` + +Optional. Application security groups in which the private endpoint IP configuration is included. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.customDnsConfigs` + +Optional. Custom DNS configurations. + +- Required: No +- Type: array + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`fqdn`](#parameter-privateendpointscustomdnsconfigsfqdn) | No | string | | +| [`ipAddresses`](#parameter-privateendpointscustomdnsconfigsipaddresses) | Yes | array | | + +### Parameter: `privateEndpoints.customDnsConfigs.fqdn` +- Required: No +- Type: string + +### Parameter: `privateEndpoints.customDnsConfigs.ipAddresses` +- Required: Yes +- Type: array + + +### Parameter: `privateEndpoints.customNetworkInterfaceName` + +Optional. The custom name of the network interface attached to the private endpoint. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.enableTelemetry` + +Optional. Enable/Disable usage telemetry for module. + +- Required: No +- Type: bool + +### Parameter: `privateEndpoints.ipConfigurations` + +Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints. + +- Required: No +- Type: array + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`groupId`](#parameter-privateendpointsipconfigurationsgroupid) | Yes | string | | +| [`memberName`](#parameter-privateendpointsipconfigurationsmembername) | Yes | string | | +| [`name`](#parameter-privateendpointsipconfigurationsname) | Yes | string | | +| [`privateIpAddress`](#parameter-privateendpointsipconfigurationsprivateipaddress) | Yes | string | | + +### Parameter: `privateEndpoints.ipConfigurations.groupId` +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.ipConfigurations.memberName` +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.ipConfigurations.name` +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.ipConfigurations.privateIpAddress` +- Required: Yes +- Type: string + + +### Parameter: `privateEndpoints.location` + +Optional. The location to deploy the private endpoint to. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.lock` + +Optional. Specify the type of lock. + +- Required: No +- Type: object + +### Parameter: `privateEndpoints.manualPrivateLinkServiceConnections` + +Optional. Manual PrivateLink Service Connections. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.name` + +Optional. The name of the private endpoint. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.privateDnsZoneGroupName` + +Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.privateDnsZoneResourceIds` + +Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.roleAssignments` + +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'. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.service` + +Required. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob". + +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.subnetResourceId` + +Required. Resource ID of the subnet where the endpoint needs to be created. + +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.tags` + +Optional. Tags to be applied on all resources/resource groups in this deployment. + +- Required: No +- Type: object ### Parameter: `privateLinkConfigurations` diff --git a/modules/network/application-gateway/main.bicep b/modules/network/application-gateway/main.bicep index e354836b98..caa78a6b4e 100644 --- a/modules/network/application-gateway/main.bicep +++ b/modules/network/application-gateway/main.bicep @@ -61,7 +61,7 @@ param httpListeners array = [] param loadDistributionPolicies array = [] @description('Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible.') -param privateEndpoints array = [] +param privateEndpoints privateEndpointType @description('Optional. PrivateLink configurations on application gateway.') param privateLinkConfigurations array = [] @@ -368,27 +368,27 @@ resource applicationGateway_diagnosticSettingName 'Microsoft.Insights/diagnostic scope: applicationGateway } -module applicationGateway_privateEndpoints '../../network/private-endpoint/main.bicep' = [for (privateEndpoint, index) in privateEndpoints: { - name: '${uniqueString(deployment().name, location)}-ApplicationGateway-PrivateEndpoint-${index}' +module applicationGateway_privateEndpoints '../../network/private-endpoint/main.bicep' = [for (privateEndpoint, index) in (privateEndpoints ?? []): { + name: '${uniqueString(deployment().name, location)}-applicationGateway-PrivateEndpoint-${index}' params: { groupIds: [ privateEndpoint.service ] - name: contains(privateEndpoint, 'name') ? privateEndpoint.name : 'pe-${last(split(applicationGateway.id, '/'))}-${privateEndpoint.service}-${index}' + name: privateEndpoint.?name ?? 'pep-${last(split(applicationGateway.id, '/'))}-${privateEndpoint.?service ?? 'account'}-${index}' serviceResourceId: applicationGateway.id subnetResourceId: privateEndpoint.subnetResourceId - enableDefaultTelemetry: enableReferencedModulesTelemetry - location: contains(privateEndpoint, 'location') ? privateEndpoint.location : reference(split(privateEndpoint.subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location + enableDefaultTelemetry: privateEndpoint.?enableDefaultTelemetry ?? enableReferencedModulesTelemetry + location: privateEndpoint.?location ?? reference(split(privateEndpoint.subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location lock: privateEndpoint.?lock ?? lock - privateDnsZoneGroupName: contains(privateEndpoint, 'privateDnsZoneGroupName') ? privateEndpoint.privateDnsZoneGroupName : 'default' - privateDnsZoneResourceIds: contains(privateEndpoint, 'privateDnsZoneResourceIds') ? privateEndpoint.privateDnsZoneResourceIds : [] - 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 : [] - applicationSecurityGroupResourceIds: contains(privateEndpoint, 'applicationSecurityGroupResourceIds') ? privateEndpoint.applicationSecurityGroupResourceIds : [] - customNetworkInterfaceName: contains(privateEndpoint, 'customNetworkInterfaceName') ? privateEndpoint.customNetworkInterfaceName : '' + privateDnsZoneGroupName: privateEndpoint.?privateDnsZoneGroupName + privateDnsZoneResourceIds: privateEndpoint.?privateDnsZoneResourceIds + roleAssignments: privateEndpoint.?roleAssignments + tags: privateEndpoint.?tags ?? tags + manualPrivateLinkServiceConnections: privateEndpoint.?manualPrivateLinkServiceConnections + customDnsConfigs: privateEndpoint.?customDnsConfigs + ipConfigurations: privateEndpoint.?ipConfigurations + applicationSecurityGroupResourceIds: privateEndpoint.?applicationSecurityGroupResourceIds + customNetworkInterfaceName: privateEndpoint.?customNetworkInterfaceName } }] @@ -452,3 +452,58 @@ type roleAssignmentType = { @description('Optional. The Resource Id of the delegated managed identity resource.') delegatedManagedIdentityResourceId: string? }[]? + +type privateEndpointType = { + @description('Optional. The name of the private endpoint.') + name: string? + + @description('Optional. The location to deploy the private endpoint to.') + location: string? + + @description('Required. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob".') + service: string + + @description('Required. Resource ID of the subnet where the endpoint needs to be created.') + subnetResourceId: string + + @description('Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided.') + privateDnsZoneGroupName: string? + + @description('Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones.') + privateDnsZoneResourceIds: string[]? + + @description('Optional. Custom DNS configurations.') + customDnsConfigs: { + fqdn: string? + ipAddresses: string[] + }[]? + + @description('Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints.') + ipConfigurations: { + name: string + groupId: string + memberName: string + privateIpAddress: string + }[]? + + @description('Optional. Application security groups in which the private endpoint IP configuration is included.') + applicationSecurityGroupResourceIds: string[]? + + @description('Optional. The custom name of the network interface attached to the private endpoint.') + customNetworkInterfaceName: string? + + @description('Optional. Specify the type of lock.') + lock: lockType + + @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\'.') + roleAssignments: roleAssignmentType + + @description('Optional. Tags to be applied on all resources/resource groups in this deployment.') + tags: object? + + @description('Optional. Manual PrivateLink Service Connections.') + manualPrivateLinkServiceConnections: array? + + @description('Optional. Enable/Disable usage telemetry for module.') + enableTelemetry: bool? +}[]? diff --git a/modules/network/application-gateway/main.json b/modules/network/application-gateway/main.json index 89f19b3046..e252642bae 100644 --- a/modules/network/application-gateway/main.json +++ b/modules/network/application-gateway/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.22.6.54827", - "templateHash": "15010715914019570085" + "templateHash": "7698802694566300060" }, "name": "Network Application Gateways", "description": "This module deploys a Network Application Gateway.", @@ -103,6 +103,154 @@ } }, "nullable": true + }, + "privateEndpointType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private endpoint." + } + }, + "location": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The location to deploy the private endpoint to." + } + }, + "service": { + "type": "string", + "metadata": { + "description": "Required. The service (sub-) type to deploy the private endpoint for. For example \"vault\" or \"blob\"." + } + }, + "subnetResourceId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the subnet where the endpoint needs to be created." + } + }, + "privateDnsZoneGroupName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided." + } + }, + "privateDnsZoneResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones." + } + }, + "customDnsConfigs": { + "type": "array", + "items": { + "type": "object", + "properties": { + "fqdn": { + "type": "string", + "nullable": true + }, + "ipAddresses": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. Custom DNS configurations." + } + }, + "ipConfigurations": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "groupId": { + "type": "string" + }, + "memberName": { + "type": "string" + }, + "privateIpAddress": { + "type": "string" + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints." + } + }, + "applicationSecurityGroupResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. Application security groups in which the private endpoint IP configuration is included." + } + }, + "customNetworkInterfaceName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The custom name of the network interface attached to the private endpoint." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "metadata": { + "description": "Optional. Specify the type of lock." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "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'." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags to be applied on all resources/resource groups in this deployment." + } + }, + "manualPrivateLinkServiceConnections": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. Manual PrivateLink Service Connections." + } + }, + "enableTelemetry": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + } + } + }, + "nullable": true } }, "parameters": { @@ -240,8 +388,7 @@ } }, "privateEndpoints": { - "type": "array", - "defaultValue": [], + "$ref": "#/definitions/privateEndpointType", "metadata": { "description": "Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible." } @@ -660,11 +807,11 @@ "applicationGateway_privateEndpoints": { "copy": { "name": "applicationGateway_privateEndpoints", - "count": "[length(parameters('privateEndpoints'))]" + "count": "[length(coalesce(parameters('privateEndpoints'), createArray()))]" }, "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", - "name": "[format('{0}-ApplicationGateway-PrivateEndpoint-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "name": "[format('{0}-applicationGateway-PrivateEndpoint-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", "properties": { "expressionEvaluationOptions": { "scope": "inner" @@ -673,32 +820,54 @@ "parameters": { "groupIds": { "value": [ - "[parameters('privateEndpoints')[copyIndex()].service]" + "[coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].service]" ] }, - "name": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'name'), createObject('value', parameters('privateEndpoints')[copyIndex()].name), createObject('value', format('pe-{0}-{1}-{2}', last(split(resourceId('Microsoft.Network/applicationGateways', parameters('name')), '/')), parameters('privateEndpoints')[copyIndex()].service, copyIndex())))]", + "name": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'name'), format('pep-{0}-{1}-{2}', last(split(resourceId('Microsoft.Network/applicationGateways', parameters('name')), '/')), coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'account'), copyIndex()))]" + }, "serviceResourceId": { "value": "[resourceId('Microsoft.Network/applicationGateways', parameters('name'))]" }, "subnetResourceId": { - "value": "[parameters('privateEndpoints')[copyIndex()].subnetResourceId]" + "value": "[coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId]" }, "enableDefaultTelemetry": { - "value": "[variables('enableReferencedModulesTelemetry')]" + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'enableDefaultTelemetry'), variables('enableReferencedModulesTelemetry'))]" + }, + "location": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'location'), reference(split(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location)]" }, - "location": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'location'), createObject('value', parameters('privateEndpoints')[copyIndex()].location), createObject('value', reference(split(parameters('privateEndpoints')[copyIndex()].subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location))]", "lock": { - "value": "[coalesce(tryGet(parameters('privateEndpoints')[copyIndex()], 'lock'), parameters('lock'))]" + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'lock'), parameters('lock'))]" + }, + "privateDnsZoneGroupName": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneGroupName')]" + }, + "privateDnsZoneResourceIds": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneResourceIds')]" + }, + "roleAssignments": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'roleAssignments')]" }, - "privateDnsZoneGroupName": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'privateDnsZoneGroupName'), createObject('value', parameters('privateEndpoints')[copyIndex()].privateDnsZoneGroupName), createObject('value', 'default'))]", - "privateDnsZoneResourceIds": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'privateDnsZoneResourceIds'), createObject('value', parameters('privateEndpoints')[copyIndex()].privateDnsZoneResourceIds), createObject('value', createArray()))]", - "roleAssignments": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'roleAssignments'), createObject('value', parameters('privateEndpoints')[copyIndex()].roleAssignments), createObject('value', createArray()))]", - "tags": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'tags'), createObject('value', parameters('privateEndpoints')[copyIndex()].tags), createObject('value', createObject()))]", - "manualPrivateLinkServiceConnections": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'manualPrivateLinkServiceConnections'), createObject('value', parameters('privateEndpoints')[copyIndex()].manualPrivateLinkServiceConnections), createObject('value', createArray()))]", - "customDnsConfigs": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'customDnsConfigs'), createObject('value', parameters('privateEndpoints')[copyIndex()].customDnsConfigs), createObject('value', createArray()))]", - "ipConfigurations": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'ipConfigurations'), createObject('value', parameters('privateEndpoints')[copyIndex()].ipConfigurations), createObject('value', createArray()))]", - "applicationSecurityGroupResourceIds": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'applicationSecurityGroupResourceIds'), createObject('value', parameters('privateEndpoints')[copyIndex()].applicationSecurityGroupResourceIds), createObject('value', createArray()))]", - "customNetworkInterfaceName": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'customNetworkInterfaceName'), createObject('value', parameters('privateEndpoints')[copyIndex()].customNetworkInterfaceName), createObject('value', ''))]" + "tags": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'tags'), parameters('tags'))]" + }, + "manualPrivateLinkServiceConnections": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'manualPrivateLinkServiceConnections')]" + }, + "customDnsConfigs": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customDnsConfigs')]" + }, + "ipConfigurations": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'ipConfigurations')]" + }, + "applicationSecurityGroupResourceIds": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'applicationSecurityGroupResourceIds')]" + }, + "customNetworkInterfaceName": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customNetworkInterfaceName')]" + } }, "template": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", diff --git a/modules/purview/account/main.bicep b/modules/purview/account/main.bicep index 4cd40ed8fc..7b15416b63 100644 --- a/modules/purview/account/main.bicep +++ b/modules/purview/account/main.bicep @@ -177,7 +177,7 @@ module account_privateEndpoints '../../network/private-endpoint/main.bicep' = [f groupIds: [ privateEndpoint.service ] - name: contains(privateEndpoint, 'name') ? privateEndpoint.name : 'pe-${last(split(account.id, '/'))}-${privateEndpoint.service}-${index}' + name: privateEndpoint.?name ?? 'pep-${last(split(account.id, '/'))}-${privateEndpoint.?service ?? privateEndpoint.service}-${index}' serviceResourceId: account.id subnetResourceId: privateEndpoint.subnetResourceId enableDefaultTelemetry: enableReferencedModulesTelemetry @@ -201,7 +201,7 @@ module portal_privateEndpoints '../../network/private-endpoint/main.bicep' = [fo groupIds: [ privateEndpoint.service ] - name: contains(privateEndpoint, 'name') ? privateEndpoint.name : 'pe-${last(split(account.id, '/'))}-${privateEndpoint.service}-${index}' + name: privateEndpoint.?name ?? 'pep-${last(split(account.id, '/'))}-${privateEndpoint.?service ?? privateEndpoint.service}-${index}' serviceResourceId: account.id subnetResourceId: privateEndpoint.subnetResourceId enableDefaultTelemetry: enableReferencedModulesTelemetry @@ -225,7 +225,7 @@ module blob_privateEndpoints '../../network/private-endpoint/main.bicep' = [for groupIds: [ privateEndpoint.service ] - name: contains(privateEndpoint, 'name') ? privateEndpoint.name : 'pe-${last(split(account.id, '/'))}-${privateEndpoint.service}-${index}' + name: privateEndpoint.?name ?? 'pep-${last(split(account.id, '/'))}-${privateEndpoint.?service ?? privateEndpoint.service}-${index}' serviceResourceId: account.properties.managedResources.storageAccount subnetResourceId: privateEndpoint.subnetResourceId enableDefaultTelemetry: enableReferencedModulesTelemetry @@ -249,7 +249,7 @@ module queue_privateEndpoints '../../network/private-endpoint/main.bicep' = [for groupIds: [ privateEndpoint.service ] - name: contains(privateEndpoint, 'name') ? privateEndpoint.name : 'pe-${last(split(account.id, '/'))}-${privateEndpoint.service}-${index}' + name: privateEndpoint.?name ?? 'pep-${last(split(account.id, '/'))}-${privateEndpoint.?service ?? privateEndpoint.service}-${index}' serviceResourceId: account.properties.managedResources.storageAccount subnetResourceId: privateEndpoint.subnetResourceId enableDefaultTelemetry: enableReferencedModulesTelemetry @@ -273,7 +273,7 @@ module eventHub_privateEndpoints '../../network/private-endpoint/main.bicep' = [ groupIds: [ privateEndpoint.service ] - name: contains(privateEndpoint, 'name') ? privateEndpoint.name : 'pe-${last(split(account.id, '/'))}-${privateEndpoint.service}-${index}' + name: privateEndpoint.?name ?? 'pep-${last(split(account.id, '/'))}-${privateEndpoint.?service ?? privateEndpoint.service}-${index}' serviceResourceId: account.properties.managedResources.eventHubNamespace subnetResourceId: privateEndpoint.subnetResourceId enableDefaultTelemetry: enableReferencedModulesTelemetry diff --git a/modules/recovery-services/vault/.test/common/main.test.bicep b/modules/recovery-services/vault/.test/common/main.test.bicep index acd15c2819..1cf146b1e3 100644 --- a/modules/recovery-services/vault/.test/common/main.test.bicep +++ b/modules/recovery-services/vault/.test/common/main.test.bicep @@ -323,11 +323,8 @@ module testDeployment '../../main.bicep' = { privateEndpoints: [ { privateDnsZoneResourceIds: [ - nestedDependencies.outputs.privateDNSZoneResourceId - ] - service: 'AzureSiteRecovery' subnetResourceId: nestedDependencies.outputs.subnetResourceId tags: { 'hidden-title': 'This is visible in the resource name' diff --git a/modules/recovery-services/vault/README.md b/modules/recovery-services/vault/README.md index b8853bbe5e..710a8b9d78 100644 --- a/modules/recovery-services/vault/README.md +++ b/modules/recovery-services/vault/README.md @@ -319,7 +319,6 @@ module vault 'br:bicep/modules/recovery-services.vault:1.0.0' = { privateDnsZoneResourceIds: [ '' ] - service: 'AzureSiteRecovery' subnetResourceId: '' tags: { Environment: 'Non-Prod' @@ -654,7 +653,6 @@ module vault 'br:bicep/modules/recovery-services.vault:1.0.0' = { "privateDnsZoneResourceIds": [ "" ], - "service": "AzureSiteRecovery", "subnetResourceId": "", "tags": { "Environment": "Non-Prod", @@ -1092,7 +1090,168 @@ Name of the Azure Recovery Service Vault. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible. - Required: No - Type: array -- Default: `[]` + + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`applicationSecurityGroupResourceIds`](#parameter-privateendpointsapplicationsecuritygroupresourceids) | No | array | Optional. Application security groups in which the private endpoint IP configuration is included. | +| [`customDnsConfigs`](#parameter-privateendpointscustomdnsconfigs) | No | array | Optional. Custom DNS configurations. | +| [`customNetworkInterfaceName`](#parameter-privateendpointscustomnetworkinterfacename) | No | string | Optional. The custom name of the network interface attached to the private endpoint. | +| [`enableTelemetry`](#parameter-privateendpointsenabletelemetry) | No | bool | Optional. Enable/Disable usage telemetry for module. | +| [`ipConfigurations`](#parameter-privateendpointsipconfigurations) | No | array | Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints. | +| [`location`](#parameter-privateendpointslocation) | No | string | Optional. The location to deploy the private endpoint to. | +| [`lock`](#parameter-privateendpointslock) | No | object | Optional. Specify the type of lock. | +| [`manualPrivateLinkServiceConnections`](#parameter-privateendpointsmanualprivatelinkserviceconnections) | No | array | Optional. Manual PrivateLink Service Connections. | +| [`name`](#parameter-privateendpointsname) | No | string | Optional. The name of the private endpoint. | +| [`privateDnsZoneGroupName`](#parameter-privateendpointsprivatednszonegroupname) | No | string | Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided. | +| [`privateDnsZoneResourceIds`](#parameter-privateendpointsprivatednszoneresourceids) | No | array | Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones. | +| [`roleAssignments`](#parameter-privateendpointsroleassignments) | No | array | 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'. | +| [`service`](#parameter-privateendpointsservice) | No | string | Optional. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob". | +| [`subnetResourceId`](#parameter-privateendpointssubnetresourceid) | Yes | string | Required. Resource ID of the subnet where the endpoint needs to be created. | +| [`tags`](#parameter-privateendpointstags) | No | object | Optional. Tags to be applied on all resources/resource groups in this deployment. | + +### Parameter: `privateEndpoints.applicationSecurityGroupResourceIds` + +Optional. Application security groups in which the private endpoint IP configuration is included. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.customDnsConfigs` + +Optional. Custom DNS configurations. + +- Required: No +- Type: array + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`fqdn`](#parameter-privateendpointscustomdnsconfigsfqdn) | No | string | | +| [`ipAddresses`](#parameter-privateendpointscustomdnsconfigsipaddresses) | Yes | array | | + +### Parameter: `privateEndpoints.customDnsConfigs.fqdn` +- Required: No +- Type: string + +### Parameter: `privateEndpoints.customDnsConfigs.ipAddresses` +- Required: Yes +- Type: array + + +### Parameter: `privateEndpoints.customNetworkInterfaceName` + +Optional. The custom name of the network interface attached to the private endpoint. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.enableTelemetry` + +Optional. Enable/Disable usage telemetry for module. + +- Required: No +- Type: bool + +### Parameter: `privateEndpoints.ipConfigurations` + +Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints. + +- Required: No +- Type: array + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`groupId`](#parameter-privateendpointsipconfigurationsgroupid) | Yes | string | | +| [`memberName`](#parameter-privateendpointsipconfigurationsmembername) | Yes | string | | +| [`name`](#parameter-privateendpointsipconfigurationsname) | Yes | string | | +| [`privateIpAddress`](#parameter-privateendpointsipconfigurationsprivateipaddress) | Yes | string | | + +### Parameter: `privateEndpoints.ipConfigurations.groupId` +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.ipConfigurations.memberName` +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.ipConfigurations.name` +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.ipConfigurations.privateIpAddress` +- Required: Yes +- Type: string + + +### Parameter: `privateEndpoints.location` + +Optional. The location to deploy the private endpoint to. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.lock` + +Optional. Specify the type of lock. + +- Required: No +- Type: object + +### Parameter: `privateEndpoints.manualPrivateLinkServiceConnections` + +Optional. Manual PrivateLink Service Connections. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.name` + +Optional. The name of the private endpoint. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.privateDnsZoneGroupName` + +Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.privateDnsZoneResourceIds` + +Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.roleAssignments` + +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'. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.service` + +Optional. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob". + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.subnetResourceId` + +Required. Resource ID of the subnet where the endpoint needs to be created. + +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.tags` + +Optional. Tags to be applied on all resources/resource groups in this deployment. + +- Required: No +- Type: object ### Parameter: `protectionContainers` diff --git a/modules/recovery-services/vault/main.bicep b/modules/recovery-services/vault/main.bicep index 54fa5cdfc4..9f72358012 100644 --- a/modules/recovery-services/vault/main.bicep +++ b/modules/recovery-services/vault/main.bicep @@ -97,7 +97,7 @@ param diagnosticMetricsToEnable array = [ param diagnosticSettingsName string = '' @description('Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible.') -param privateEndpoints array = [] +param privateEndpoints privateEndpointType @description('Optional. Monitoring Settings of the vault.') param monitoringSettings object = {} @@ -293,27 +293,27 @@ resource rsv_diagnosticSettings 'Microsoft.Insights/diagnosticSettings@2021-05-0 scope: rsv } -module rsv_privateEndpoints '../../network/private-endpoint/main.bicep' = [for (privateEndpoint, index) in privateEndpoints: { - name: '${uniqueString(deployment().name, location)}-RSV-PrivateEndpoint-${index}' +module rsv_privateEndpoints '../../network/private-endpoint/main.bicep' = [for (privateEndpoint, index) in (privateEndpoints ?? []): { + name: '${uniqueString(deployment().name, location)}-rsv-PrivateEndpoint-${index}' params: { groupIds: [ - privateEndpoint.service + privateEndpoint.?service ?? 'AzureSiteRecovery' ] - name: contains(privateEndpoint, 'name') ? privateEndpoint.name : 'pe-${last(split(rsv.id, '/'))}-${privateEndpoint.service}-${index}' + name: privateEndpoint.?name ?? 'pep-${last(split(rsv.id, '/'))}-${privateEndpoint.?service ?? 'AzureSiteRecovery'}-${index}' serviceResourceId: rsv.id subnetResourceId: privateEndpoint.subnetResourceId - enableDefaultTelemetry: enableReferencedModulesTelemetry - location: contains(privateEndpoint, 'location') ? privateEndpoint.location : reference(split(privateEndpoint.subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location + enableDefaultTelemetry: privateEndpoint.?enableDefaultTelemetry ?? enableReferencedModulesTelemetry + location: privateEndpoint.?location ?? reference(split(privateEndpoint.subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location lock: privateEndpoint.?lock ?? lock - privateDnsZoneGroupName: contains(privateEndpoint, 'privateDnsZoneGroupName') ? privateEndpoint.privateDnsZoneGroupName : 'default' - privateDnsZoneResourceIds: contains(privateEndpoint, 'privateDnsZoneResourceIds') ? privateEndpoint.privateDnsZoneResourceIds : [] - 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 : [] - applicationSecurityGroupResourceIds: contains(privateEndpoint, 'applicationSecurityGroupResourceIds') ? privateEndpoint.applicationSecurityGroupResourceIds : [] - customNetworkInterfaceName: contains(privateEndpoint, 'customNetworkInterfaceName') ? privateEndpoint.customNetworkInterfaceName : '' + privateDnsZoneGroupName: privateEndpoint.?privateDnsZoneGroupName + privateDnsZoneResourceIds: privateEndpoint.?privateDnsZoneResourceIds + roleAssignments: privateEndpoint.?roleAssignments + tags: privateEndpoint.?tags ?? tags + manualPrivateLinkServiceConnections: privateEndpoint.?manualPrivateLinkServiceConnections + customDnsConfigs: privateEndpoint.?customDnsConfigs + ipConfigurations: privateEndpoint.?ipConfigurations + applicationSecurityGroupResourceIds: privateEndpoint.?applicationSecurityGroupResourceIds + customNetworkInterfaceName: privateEndpoint.?customNetworkInterfaceName } }] @@ -380,3 +380,58 @@ type roleAssignmentType = { @description('Optional. The Resource Id of the delegated managed identity resource.') delegatedManagedIdentityResourceId: string? }[]? + +type privateEndpointType = { + @description('Optional. The name of the private endpoint.') + name: string? + + @description('Optional. The location to deploy the private endpoint to.') + location: string? + + @description('Optional. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob".') + service: string? + + @description('Required. Resource ID of the subnet where the endpoint needs to be created.') + subnetResourceId: string + + @description('Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided.') + privateDnsZoneGroupName: string? + + @description('Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones.') + privateDnsZoneResourceIds: string[]? + + @description('Optional. Custom DNS configurations.') + customDnsConfigs: { + fqdn: string? + ipAddresses: string[] + }[]? + + @description('Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints.') + ipConfigurations: { + name: string + groupId: string + memberName: string + privateIpAddress: string + }[]? + + @description('Optional. Application security groups in which the private endpoint IP configuration is included.') + applicationSecurityGroupResourceIds: string[]? + + @description('Optional. The custom name of the network interface attached to the private endpoint.') + customNetworkInterfaceName: string? + + @description('Optional. Specify the type of lock.') + lock: lockType + + @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\'.') + roleAssignments: roleAssignmentType + + @description('Optional. Tags to be applied on all resources/resource groups in this deployment.') + tags: object? + + @description('Optional. Manual PrivateLink Service Connections.') + manualPrivateLinkServiceConnections: array? + + @description('Optional. Enable/Disable usage telemetry for module.') + enableTelemetry: bool? +}[]? diff --git a/modules/recovery-services/vault/main.json b/modules/recovery-services/vault/main.json index a511b9ec81..7750cd92af 100644 --- a/modules/recovery-services/vault/main.json +++ b/modules/recovery-services/vault/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.22.6.54827", - "templateHash": "5878546840192732516" + "templateHash": "18071219437488325472" }, "name": "Recovery Services Vaults", "description": "This module deploys a Recovery Services Vault.", @@ -103,6 +103,155 @@ } }, "nullable": true + }, + "privateEndpointType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private endpoint." + } + }, + "location": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The location to deploy the private endpoint to." + } + }, + "service": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The service (sub-) type to deploy the private endpoint for. For example \"vault\" or \"blob\"." + } + }, + "subnetResourceId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the subnet where the endpoint needs to be created." + } + }, + "privateDnsZoneGroupName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided." + } + }, + "privateDnsZoneResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones." + } + }, + "customDnsConfigs": { + "type": "array", + "items": { + "type": "object", + "properties": { + "fqdn": { + "type": "string", + "nullable": true + }, + "ipAddresses": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. Custom DNS configurations." + } + }, + "ipConfigurations": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "groupId": { + "type": "string" + }, + "memberName": { + "type": "string" + }, + "privateIpAddress": { + "type": "string" + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints." + } + }, + "applicationSecurityGroupResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. Application security groups in which the private endpoint IP configuration is included." + } + }, + "customNetworkInterfaceName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The custom name of the network interface attached to the private endpoint." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "metadata": { + "description": "Optional. Specify the type of lock." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "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'." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags to be applied on all resources/resource groups in this deployment." + } + }, + "manualPrivateLinkServiceConnections": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. Manual PrivateLink Service Connections." + } + }, + "enableTelemetry": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + } + } + }, + "nullable": true } }, "parameters": { @@ -286,8 +435,7 @@ } }, "privateEndpoints": { - "type": "array", - "defaultValue": [], + "$ref": "#/definitions/privateEndpointType", "metadata": { "description": "Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible." } @@ -1999,11 +2147,11 @@ "rsv_privateEndpoints": { "copy": { "name": "rsv_privateEndpoints", - "count": "[length(parameters('privateEndpoints'))]" + "count": "[length(coalesce(parameters('privateEndpoints'), createArray()))]" }, "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", - "name": "[format('{0}-RSV-PrivateEndpoint-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "name": "[format('{0}-rsv-PrivateEndpoint-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", "properties": { "expressionEvaluationOptions": { "scope": "inner" @@ -2012,32 +2160,54 @@ "parameters": { "groupIds": { "value": [ - "[parameters('privateEndpoints')[copyIndex()].service]" + "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'AzureSiteRecovery')]" ] }, - "name": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'name'), createObject('value', parameters('privateEndpoints')[copyIndex()].name), createObject('value', format('pe-{0}-{1}-{2}', last(split(resourceId('Microsoft.RecoveryServices/vaults', parameters('name')), '/')), parameters('privateEndpoints')[copyIndex()].service, copyIndex())))]", + "name": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'name'), format('pep-{0}-{1}-{2}', last(split(resourceId('Microsoft.RecoveryServices/vaults', parameters('name')), '/')), coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'AzureSiteRecovery'), copyIndex()))]" + }, "serviceResourceId": { "value": "[resourceId('Microsoft.RecoveryServices/vaults', parameters('name'))]" }, "subnetResourceId": { - "value": "[parameters('privateEndpoints')[copyIndex()].subnetResourceId]" + "value": "[coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId]" }, "enableDefaultTelemetry": { - "value": "[variables('enableReferencedModulesTelemetry')]" + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'enableDefaultTelemetry'), variables('enableReferencedModulesTelemetry'))]" + }, + "location": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'location'), reference(split(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location)]" }, - "location": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'location'), createObject('value', parameters('privateEndpoints')[copyIndex()].location), createObject('value', reference(split(parameters('privateEndpoints')[copyIndex()].subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location))]", "lock": { - "value": "[coalesce(tryGet(parameters('privateEndpoints')[copyIndex()], 'lock'), parameters('lock'))]" - }, - "privateDnsZoneGroupName": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'privateDnsZoneGroupName'), createObject('value', parameters('privateEndpoints')[copyIndex()].privateDnsZoneGroupName), createObject('value', 'default'))]", - "privateDnsZoneResourceIds": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'privateDnsZoneResourceIds'), createObject('value', parameters('privateEndpoints')[copyIndex()].privateDnsZoneResourceIds), createObject('value', createArray()))]", - "roleAssignments": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'roleAssignments'), createObject('value', parameters('privateEndpoints')[copyIndex()].roleAssignments), createObject('value', createArray()))]", - "tags": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'tags'), createObject('value', parameters('privateEndpoints')[copyIndex()].tags), createObject('value', createObject()))]", - "manualPrivateLinkServiceConnections": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'manualPrivateLinkServiceConnections'), createObject('value', parameters('privateEndpoints')[copyIndex()].manualPrivateLinkServiceConnections), createObject('value', createArray()))]", - "customDnsConfigs": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'customDnsConfigs'), createObject('value', parameters('privateEndpoints')[copyIndex()].customDnsConfigs), createObject('value', createArray()))]", - "ipConfigurations": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'ipConfigurations'), createObject('value', parameters('privateEndpoints')[copyIndex()].ipConfigurations), createObject('value', createArray()))]", - "applicationSecurityGroupResourceIds": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'applicationSecurityGroupResourceIds'), createObject('value', parameters('privateEndpoints')[copyIndex()].applicationSecurityGroupResourceIds), createObject('value', createArray()))]", - "customNetworkInterfaceName": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'customNetworkInterfaceName'), createObject('value', parameters('privateEndpoints')[copyIndex()].customNetworkInterfaceName), createObject('value', ''))]" + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'lock'), parameters('lock'))]" + }, + "privateDnsZoneGroupName": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneGroupName')]" + }, + "privateDnsZoneResourceIds": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneResourceIds')]" + }, + "roleAssignments": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'roleAssignments')]" + }, + "tags": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'tags'), parameters('tags'))]" + }, + "manualPrivateLinkServiceConnections": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'manualPrivateLinkServiceConnections')]" + }, + "customDnsConfigs": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customDnsConfigs')]" + }, + "ipConfigurations": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'ipConfigurations')]" + }, + "applicationSecurityGroupResourceIds": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'applicationSecurityGroupResourceIds')]" + }, + "customNetworkInterfaceName": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customNetworkInterfaceName')]" + } }, "template": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", diff --git a/modules/relay/namespace/.test/pe/main.test.bicep b/modules/relay/namespace/.test/pe/main.test.bicep index 380e33d618..c982c0e55d 100644 --- a/modules/relay/namespace/.test/pe/main.test.bicep +++ b/modules/relay/namespace/.test/pe/main.test.bicep @@ -52,7 +52,6 @@ module testDeployment '../../main.bicep' = { skuName: 'Standard' privateEndpoints: [ { - service: 'namespace' subnetResourceId: nestedDependencies.outputs.subnetResourceId privateDnsZoneResourceIds: [ nestedDependencies.outputs.privateDNSZoneResourceId diff --git a/modules/relay/namespace/README.md b/modules/relay/namespace/README.md index 9d8a11f544..96fdc19002 100644 --- a/modules/relay/namespace/README.md +++ b/modules/relay/namespace/README.md @@ -380,7 +380,6 @@ module namespace 'br:bicep/modules/relay.namespace:1.0.0' = { privateDnsZoneResourceIds: [ '' ] - service: 'namespace' subnetResourceId: '' tags: { Environment: 'Non-Prod' @@ -425,7 +424,6 @@ module namespace 'br:bicep/modules/relay.namespace:1.0.0' = { "privateDnsZoneResourceIds": [ "" ], - "service": "namespace", "subnetResourceId": "", "tags": { "Environment": "Non-Prod", @@ -608,7 +606,168 @@ Configure networking options for Relay. This object contains IPs/Subnets to allo Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible. - Required: No - Type: array -- Default: `[]` + + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`applicationSecurityGroupResourceIds`](#parameter-privateendpointsapplicationsecuritygroupresourceids) | No | array | Optional. Application security groups in which the private endpoint IP configuration is included. | +| [`customDnsConfigs`](#parameter-privateendpointscustomdnsconfigs) | No | array | Optional. Custom DNS configurations. | +| [`customNetworkInterfaceName`](#parameter-privateendpointscustomnetworkinterfacename) | No | string | Optional. The custom name of the network interface attached to the private endpoint. | +| [`enableTelemetry`](#parameter-privateendpointsenabletelemetry) | No | bool | Optional. Enable/Disable usage telemetry for module. | +| [`ipConfigurations`](#parameter-privateendpointsipconfigurations) | No | array | Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints. | +| [`location`](#parameter-privateendpointslocation) | No | string | Optional. The location to deploy the private endpoint to. | +| [`lock`](#parameter-privateendpointslock) | No | object | Optional. Specify the type of lock. | +| [`manualPrivateLinkServiceConnections`](#parameter-privateendpointsmanualprivatelinkserviceconnections) | No | array | Optional. Manual PrivateLink Service Connections. | +| [`name`](#parameter-privateendpointsname) | No | string | Optional. The name of the private endpoint. | +| [`privateDnsZoneGroupName`](#parameter-privateendpointsprivatednszonegroupname) | No | string | Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided. | +| [`privateDnsZoneResourceIds`](#parameter-privateendpointsprivatednszoneresourceids) | No | array | Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones. | +| [`roleAssignments`](#parameter-privateendpointsroleassignments) | No | array | 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'. | +| [`service`](#parameter-privateendpointsservice) | No | string | Optional. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob". | +| [`subnetResourceId`](#parameter-privateendpointssubnetresourceid) | Yes | string | Required. Resource ID of the subnet where the endpoint needs to be created. | +| [`tags`](#parameter-privateendpointstags) | No | object | Optional. Tags to be applied on all resources/resource groups in this deployment. | + +### Parameter: `privateEndpoints.applicationSecurityGroupResourceIds` + +Optional. Application security groups in which the private endpoint IP configuration is included. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.customDnsConfigs` + +Optional. Custom DNS configurations. + +- Required: No +- Type: array + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`fqdn`](#parameter-privateendpointscustomdnsconfigsfqdn) | No | string | | +| [`ipAddresses`](#parameter-privateendpointscustomdnsconfigsipaddresses) | Yes | array | | + +### Parameter: `privateEndpoints.customDnsConfigs.fqdn` +- Required: No +- Type: string + +### Parameter: `privateEndpoints.customDnsConfigs.ipAddresses` +- Required: Yes +- Type: array + + +### Parameter: `privateEndpoints.customNetworkInterfaceName` + +Optional. The custom name of the network interface attached to the private endpoint. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.enableTelemetry` + +Optional. Enable/Disable usage telemetry for module. + +- Required: No +- Type: bool + +### Parameter: `privateEndpoints.ipConfigurations` + +Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints. + +- Required: No +- Type: array + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`groupId`](#parameter-privateendpointsipconfigurationsgroupid) | Yes | string | | +| [`memberName`](#parameter-privateendpointsipconfigurationsmembername) | Yes | string | | +| [`name`](#parameter-privateendpointsipconfigurationsname) | Yes | string | | +| [`privateIpAddress`](#parameter-privateendpointsipconfigurationsprivateipaddress) | Yes | string | | + +### Parameter: `privateEndpoints.ipConfigurations.groupId` +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.ipConfigurations.memberName` +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.ipConfigurations.name` +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.ipConfigurations.privateIpAddress` +- Required: Yes +- Type: string + + +### Parameter: `privateEndpoints.location` + +Optional. The location to deploy the private endpoint to. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.lock` + +Optional. Specify the type of lock. + +- Required: No +- Type: object + +### Parameter: `privateEndpoints.manualPrivateLinkServiceConnections` + +Optional. Manual PrivateLink Service Connections. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.name` + +Optional. The name of the private endpoint. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.privateDnsZoneGroupName` + +Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.privateDnsZoneResourceIds` + +Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.roleAssignments` + +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'. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.service` + +Optional. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob". + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.subnetResourceId` + +Required. Resource ID of the subnet where the endpoint needs to be created. + +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.tags` + +Optional. Tags to be applied on all resources/resource groups in this deployment. + +- Required: No +- Type: object ### Parameter: `roleAssignments` diff --git a/modules/relay/namespace/main.bicep b/modules/relay/namespace/main.bicep index 5ddc75500b..0f2299644b 100644 --- a/modules/relay/namespace/main.bicep +++ b/modules/relay/namespace/main.bicep @@ -47,7 +47,7 @@ param lock lockType param roleAssignments roleAssignmentType @description('Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible.') -param privateEndpoints array = [] +param privateEndpoints privateEndpointType @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 = {} @@ -251,27 +251,27 @@ resource namespace_diagnosticSettings 'Microsoft.Insights/diagnosticSettings@202 scope: namespace } -module namespace_privateEndpoints '../../network/private-endpoint/main.bicep' = [for (privateEndpoint, index) in privateEndpoints: { - name: '${uniqueString(deployment().name, location)}-Namespace-PrivateEndpoint-${index}' +module namespace_privateEndpoints '../../network/private-endpoint/main.bicep' = [for (privateEndpoint, index) in (privateEndpoints ?? []): { + name: '${uniqueString(deployment().name, location)}-namespace-PrivateEndpoint-${index}' params: { groupIds: [ - privateEndpoint.service + privateEndpoint.?service ?? 'namespace' ] - name: contains(privateEndpoint, 'name') ? privateEndpoint.name : 'pe-${last(split(namespace.id, '/'))}-${privateEndpoint.service}-${index}' + name: privateEndpoint.?name ?? 'pep-${last(split(namespace.id, '/'))}-${privateEndpoint.?service ?? 'namespace'}-${index}' serviceResourceId: namespace.id subnetResourceId: privateEndpoint.subnetResourceId - enableDefaultTelemetry: enableReferencedModulesTelemetry - location: contains(privateEndpoint, 'location') ? privateEndpoint.location : reference(split(privateEndpoint.subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location + enableDefaultTelemetry: privateEndpoint.?enableDefaultTelemetry ?? enableReferencedModulesTelemetry + location: privateEndpoint.?location ?? reference(split(privateEndpoint.subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location lock: privateEndpoint.?lock ?? lock - privateDnsZoneGroupName: contains(privateEndpoint, 'privateDnsZoneGroupName') ? privateEndpoint.privateDnsZoneGroupName : 'default' - privateDnsZoneResourceIds: contains(privateEndpoint, 'privateDnsZoneResourceIds') ? privateEndpoint.privateDnsZoneResourceIds : [] - 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 : [] - applicationSecurityGroupResourceIds: contains(privateEndpoint, 'applicationSecurityGroupResourceIds') ? privateEndpoint.applicationSecurityGroupResourceIds : [] - customNetworkInterfaceName: contains(privateEndpoint, 'customNetworkInterfaceName') ? privateEndpoint.customNetworkInterfaceName : '' + privateDnsZoneGroupName: privateEndpoint.?privateDnsZoneGroupName + privateDnsZoneResourceIds: privateEndpoint.?privateDnsZoneResourceIds + roleAssignments: privateEndpoint.?roleAssignments + tags: privateEndpoint.?tags ?? tags + manualPrivateLinkServiceConnections: privateEndpoint.?manualPrivateLinkServiceConnections + customDnsConfigs: privateEndpoint.?customDnsConfigs + ipConfigurations: privateEndpoint.?ipConfigurations + applicationSecurityGroupResourceIds: privateEndpoint.?applicationSecurityGroupResourceIds + customNetworkInterfaceName: privateEndpoint.?customNetworkInterfaceName } }] @@ -335,3 +335,58 @@ type roleAssignmentType = { @description('Optional. The Resource Id of the delegated managed identity resource.') delegatedManagedIdentityResourceId: string? }[]? + +type privateEndpointType = { + @description('Optional. The name of the private endpoint.') + name: string? + + @description('Optional. The location to deploy the private endpoint to.') + location: string? + + @description('Optional. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob".') + service: string? + + @description('Required. Resource ID of the subnet where the endpoint needs to be created.') + subnetResourceId: string + + @description('Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided.') + privateDnsZoneGroupName: string? + + @description('Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones.') + privateDnsZoneResourceIds: string[]? + + @description('Optional. Custom DNS configurations.') + customDnsConfigs: { + fqdn: string? + ipAddresses: string[] + }[]? + + @description('Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints.') + ipConfigurations: { + name: string + groupId: string + memberName: string + privateIpAddress: string + }[]? + + @description('Optional. Application security groups in which the private endpoint IP configuration is included.') + applicationSecurityGroupResourceIds: string[]? + + @description('Optional. The custom name of the network interface attached to the private endpoint.') + customNetworkInterfaceName: string? + + @description('Optional. Specify the type of lock.') + lock: lockType + + @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\'.') + roleAssignments: roleAssignmentType + + @description('Optional. Tags to be applied on all resources/resource groups in this deployment.') + tags: object? + + @description('Optional. Manual PrivateLink Service Connections.') + manualPrivateLinkServiceConnections: array? + + @description('Optional. Enable/Disable usage telemetry for module.') + enableTelemetry: bool? +}[]? diff --git a/modules/relay/namespace/main.json b/modules/relay/namespace/main.json index e3407dc5cd..6d499747c1 100644 --- a/modules/relay/namespace/main.json +++ b/modules/relay/namespace/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.22.6.54827", - "templateHash": "13773141750088228766" + "templateHash": "16916844695310222136" }, "name": "Relay Namespaces", "description": "This module deploys a Relay Namespace", @@ -103,6 +103,155 @@ } }, "nullable": true + }, + "privateEndpointType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private endpoint." + } + }, + "location": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The location to deploy the private endpoint to." + } + }, + "service": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The service (sub-) type to deploy the private endpoint for. For example \"vault\" or \"blob\"." + } + }, + "subnetResourceId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the subnet where the endpoint needs to be created." + } + }, + "privateDnsZoneGroupName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided." + } + }, + "privateDnsZoneResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones." + } + }, + "customDnsConfigs": { + "type": "array", + "items": { + "type": "object", + "properties": { + "fqdn": { + "type": "string", + "nullable": true + }, + "ipAddresses": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. Custom DNS configurations." + } + }, + "ipConfigurations": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "groupId": { + "type": "string" + }, + "memberName": { + "type": "string" + }, + "privateIpAddress": { + "type": "string" + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints." + } + }, + "applicationSecurityGroupResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. Application security groups in which the private endpoint IP configuration is included." + } + }, + "customNetworkInterfaceName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The custom name of the network interface attached to the private endpoint." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "metadata": { + "description": "Optional. Specify the type of lock." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "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'." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags to be applied on all resources/resource groups in this deployment." + } + }, + "manualPrivateLinkServiceConnections": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. Manual PrivateLink Service Connections." + } + }, + "enableTelemetry": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + } + } + }, + "nullable": true } }, "parameters": { @@ -188,8 +337,7 @@ } }, "privateEndpoints": { - "type": "array", - "defaultValue": [], + "$ref": "#/definitions/privateEndpointType", "metadata": { "description": "Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible." } @@ -1582,11 +1730,11 @@ "namespace_privateEndpoints": { "copy": { "name": "namespace_privateEndpoints", - "count": "[length(parameters('privateEndpoints'))]" + "count": "[length(coalesce(parameters('privateEndpoints'), createArray()))]" }, "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", - "name": "[format('{0}-Namespace-PrivateEndpoint-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "name": "[format('{0}-namespace-PrivateEndpoint-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", "properties": { "expressionEvaluationOptions": { "scope": "inner" @@ -1595,32 +1743,54 @@ "parameters": { "groupIds": { "value": [ - "[parameters('privateEndpoints')[copyIndex()].service]" + "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'namespace')]" ] }, - "name": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'name'), createObject('value', parameters('privateEndpoints')[copyIndex()].name), createObject('value', format('pe-{0}-{1}-{2}', last(split(resourceId('Microsoft.Relay/namespaces', parameters('name')), '/')), parameters('privateEndpoints')[copyIndex()].service, copyIndex())))]", + "name": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'name'), format('pep-{0}-{1}-{2}', last(split(resourceId('Microsoft.Relay/namespaces', parameters('name')), '/')), coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'namespace'), copyIndex()))]" + }, "serviceResourceId": { "value": "[resourceId('Microsoft.Relay/namespaces', parameters('name'))]" }, "subnetResourceId": { - "value": "[parameters('privateEndpoints')[copyIndex()].subnetResourceId]" + "value": "[coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId]" }, "enableDefaultTelemetry": { - "value": "[variables('enableReferencedModulesTelemetry')]" + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'enableDefaultTelemetry'), variables('enableReferencedModulesTelemetry'))]" + }, + "location": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'location'), reference(split(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location)]" }, - "location": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'location'), createObject('value', parameters('privateEndpoints')[copyIndex()].location), createObject('value', reference(split(parameters('privateEndpoints')[copyIndex()].subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location))]", "lock": { - "value": "[coalesce(tryGet(parameters('privateEndpoints')[copyIndex()], 'lock'), parameters('lock'))]" + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'lock'), parameters('lock'))]" + }, + "privateDnsZoneGroupName": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneGroupName')]" + }, + "privateDnsZoneResourceIds": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneResourceIds')]" + }, + "roleAssignments": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'roleAssignments')]" }, - "privateDnsZoneGroupName": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'privateDnsZoneGroupName'), createObject('value', parameters('privateEndpoints')[copyIndex()].privateDnsZoneGroupName), createObject('value', 'default'))]", - "privateDnsZoneResourceIds": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'privateDnsZoneResourceIds'), createObject('value', parameters('privateEndpoints')[copyIndex()].privateDnsZoneResourceIds), createObject('value', createArray()))]", - "roleAssignments": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'roleAssignments'), createObject('value', parameters('privateEndpoints')[copyIndex()].roleAssignments), createObject('value', createArray()))]", - "tags": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'tags'), createObject('value', parameters('privateEndpoints')[copyIndex()].tags), createObject('value', createObject()))]", - "manualPrivateLinkServiceConnections": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'manualPrivateLinkServiceConnections'), createObject('value', parameters('privateEndpoints')[copyIndex()].manualPrivateLinkServiceConnections), createObject('value', createArray()))]", - "customDnsConfigs": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'customDnsConfigs'), createObject('value', parameters('privateEndpoints')[copyIndex()].customDnsConfigs), createObject('value', createArray()))]", - "ipConfigurations": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'ipConfigurations'), createObject('value', parameters('privateEndpoints')[copyIndex()].ipConfigurations), createObject('value', createArray()))]", - "applicationSecurityGroupResourceIds": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'applicationSecurityGroupResourceIds'), createObject('value', parameters('privateEndpoints')[copyIndex()].applicationSecurityGroupResourceIds), createObject('value', createArray()))]", - "customNetworkInterfaceName": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'customNetworkInterfaceName'), createObject('value', parameters('privateEndpoints')[copyIndex()].customNetworkInterfaceName), createObject('value', ''))]" + "tags": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'tags'), parameters('tags'))]" + }, + "manualPrivateLinkServiceConnections": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'manualPrivateLinkServiceConnections')]" + }, + "customDnsConfigs": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customDnsConfigs')]" + }, + "ipConfigurations": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'ipConfigurations')]" + }, + "applicationSecurityGroupResourceIds": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'applicationSecurityGroupResourceIds')]" + }, + "customNetworkInterfaceName": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customNetworkInterfaceName')]" + } }, "template": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", diff --git a/modules/search/search-service/.test/pe/main.test.bicep b/modules/search/search-service/.test/pe/main.test.bicep index 0c4ab94004..7fe335da8f 100644 --- a/modules/search/search-service/.test/pe/main.test.bicep +++ b/modules/search/search-service/.test/pe/main.test.bicep @@ -62,7 +62,6 @@ module testDeployment '../../main.bicep' = { privateDnsZoneResourceIds: [ nestedDependencies.outputs.privateDNSZoneResourceId ] - service: 'searchService' subnetResourceId: nestedDependencies.outputs.subnetResourceId tags: { Environment: 'Non-Prod' diff --git a/modules/search/search-service/README.md b/modules/search/search-service/README.md index 50cc66d418..7dfccea700 100644 --- a/modules/search/search-service/README.md +++ b/modules/search/search-service/README.md @@ -278,7 +278,6 @@ module searchService 'br:bicep/modules/search.search-service:1.0.0' = { privateDnsZoneResourceIds: [ '' ] - service: 'searchService' subnetResourceId: '' tags: { Environment: 'Non-Prod' @@ -338,7 +337,6 @@ module searchService 'br:bicep/modules/search.search-service:1.0.0' = { "privateDnsZoneResourceIds": [ "" ], - "service": "searchService", "subnetResourceId": "", "tags": { "Environment": "Non-Prod", @@ -564,7 +562,168 @@ The number of partitions in the search service; if specified, it can be 1, 2, 3, Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible. - Required: No - Type: array -- Default: `[]` + + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`applicationSecurityGroupResourceIds`](#parameter-privateendpointsapplicationsecuritygroupresourceids) | No | array | Optional. Application security groups in which the private endpoint IP configuration is included. | +| [`customDnsConfigs`](#parameter-privateendpointscustomdnsconfigs) | No | array | Optional. Custom DNS configurations. | +| [`customNetworkInterfaceName`](#parameter-privateendpointscustomnetworkinterfacename) | No | string | Optional. The custom name of the network interface attached to the private endpoint. | +| [`enableTelemetry`](#parameter-privateendpointsenabletelemetry) | No | bool | Optional. Enable/Disable usage telemetry for module. | +| [`ipConfigurations`](#parameter-privateendpointsipconfigurations) | No | array | Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints. | +| [`location`](#parameter-privateendpointslocation) | No | string | Optional. The location to deploy the private endpoint to. | +| [`lock`](#parameter-privateendpointslock) | No | object | Optional. Specify the type of lock. | +| [`manualPrivateLinkServiceConnections`](#parameter-privateendpointsmanualprivatelinkserviceconnections) | No | array | Optional. Manual PrivateLink Service Connections. | +| [`name`](#parameter-privateendpointsname) | No | string | Optional. The name of the private endpoint. | +| [`privateDnsZoneGroupName`](#parameter-privateendpointsprivatednszonegroupname) | No | string | Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided. | +| [`privateDnsZoneResourceIds`](#parameter-privateendpointsprivatednszoneresourceids) | No | array | Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones. | +| [`roleAssignments`](#parameter-privateendpointsroleassignments) | No | array | 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'. | +| [`service`](#parameter-privateendpointsservice) | No | string | Optional. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob". | +| [`subnetResourceId`](#parameter-privateendpointssubnetresourceid) | Yes | string | Required. Resource ID of the subnet where the endpoint needs to be created. | +| [`tags`](#parameter-privateendpointstags) | No | object | Optional. Tags to be applied on all resources/resource groups in this deployment. | + +### Parameter: `privateEndpoints.applicationSecurityGroupResourceIds` + +Optional. Application security groups in which the private endpoint IP configuration is included. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.customDnsConfigs` + +Optional. Custom DNS configurations. + +- Required: No +- Type: array + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`fqdn`](#parameter-privateendpointscustomdnsconfigsfqdn) | No | string | | +| [`ipAddresses`](#parameter-privateendpointscustomdnsconfigsipaddresses) | Yes | array | | + +### Parameter: `privateEndpoints.customDnsConfigs.fqdn` +- Required: No +- Type: string + +### Parameter: `privateEndpoints.customDnsConfigs.ipAddresses` +- Required: Yes +- Type: array + + +### Parameter: `privateEndpoints.customNetworkInterfaceName` + +Optional. The custom name of the network interface attached to the private endpoint. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.enableTelemetry` + +Optional. Enable/Disable usage telemetry for module. + +- Required: No +- Type: bool + +### Parameter: `privateEndpoints.ipConfigurations` + +Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints. + +- Required: No +- Type: array + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`groupId`](#parameter-privateendpointsipconfigurationsgroupid) | Yes | string | | +| [`memberName`](#parameter-privateendpointsipconfigurationsmembername) | Yes | string | | +| [`name`](#parameter-privateendpointsipconfigurationsname) | Yes | string | | +| [`privateIpAddress`](#parameter-privateendpointsipconfigurationsprivateipaddress) | Yes | string | | + +### Parameter: `privateEndpoints.ipConfigurations.groupId` +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.ipConfigurations.memberName` +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.ipConfigurations.name` +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.ipConfigurations.privateIpAddress` +- Required: Yes +- Type: string + + +### Parameter: `privateEndpoints.location` + +Optional. The location to deploy the private endpoint to. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.lock` + +Optional. Specify the type of lock. + +- Required: No +- Type: object + +### Parameter: `privateEndpoints.manualPrivateLinkServiceConnections` + +Optional. Manual PrivateLink Service Connections. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.name` + +Optional. The name of the private endpoint. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.privateDnsZoneGroupName` + +Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.privateDnsZoneResourceIds` + +Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.roleAssignments` + +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'. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.service` + +Optional. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob". + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.subnetResourceId` + +Required. Resource ID of the subnet where the endpoint needs to be created. + +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.tags` + +Optional. Tags to be applied on all resources/resource groups in this deployment. + +- Required: No +- Type: object ### Parameter: `publicNetworkAccess` diff --git a/modules/search/search-service/main.bicep b/modules/search/search-service/main.bicep index d35e2da654..ad25223fcd 100644 --- a/modules/search/search-service/main.bicep +++ b/modules/search/search-service/main.bicep @@ -48,7 +48,7 @@ param networkRuleSet object = {} param partitionCount int = 1 @description('Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible.') -param privateEndpoints array = [] +param privateEndpoints privateEndpointType @description('Optional. The sharedPrivateLinkResources to create as part of the search Service.') param sharedPrivateLinkResources array = [] @@ -225,27 +225,27 @@ resource searchService_roleAssignments 'Microsoft.Authorization/roleAssignments@ scope: searchService }] -module searchService_privateEndpoints '../../network/private-endpoint/main.bicep' = [for (privateEndpoint, index) in privateEndpoints: { +module searchService_privateEndpoints '../../network/private-endpoint/main.bicep' = [for (privateEndpoint, index) in (privateEndpoints ?? []): { name: '${uniqueString(deployment().name, location)}-searchService-PrivateEndpoint-${index}' params: { groupIds: [ - privateEndpoint.service + privateEndpoint.?service ?? 'searchService' ] - name: contains(privateEndpoint, 'name') ? privateEndpoint.name : 'pe-${last(split(searchService.id, '/'))}-${privateEndpoint.service}-${index}' + name: privateEndpoint.?name ?? 'pep-${last(split(searchService.id, '/'))}-${privateEndpoint.?service ?? 'searchService'}-${index}' serviceResourceId: searchService.id subnetResourceId: privateEndpoint.subnetResourceId - enableDefaultTelemetry: enableReferencedModulesTelemetry - location: contains(privateEndpoint, 'location') ? privateEndpoint.location : reference(split(privateEndpoint.subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location + enableDefaultTelemetry: privateEndpoint.?enableDefaultTelemetry ?? enableReferencedModulesTelemetry + location: privateEndpoint.?location ?? reference(split(privateEndpoint.subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location lock: privateEndpoint.?lock ?? lock - privateDnsZoneGroupName: contains(privateEndpoint, 'privateDnsZoneGroupName') ? privateEndpoint.privateDnsZoneGroupName : 'default' - privateDnsZoneResourceIds: contains(privateEndpoint, 'privateDnsZoneResourceIds') ? privateEndpoint.privateDnsZoneResourceIds : [] - 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 : [] - applicationSecurityGroupResourceIds: contains(privateEndpoint, 'applicationSecurityGroupResourceIds') ? privateEndpoint.applicationSecurityGroupResourceIds : [] - customNetworkInterfaceName: contains(privateEndpoint, 'customNetworkInterfaceName') ? privateEndpoint.customNetworkInterfaceName : '' + privateDnsZoneGroupName: privateEndpoint.?privateDnsZoneGroupName + privateDnsZoneResourceIds: privateEndpoint.?privateDnsZoneResourceIds + roleAssignments: privateEndpoint.?roleAssignments + tags: privateEndpoint.?tags ?? tags + manualPrivateLinkServiceConnections: privateEndpoint.?manualPrivateLinkServiceConnections + customDnsConfigs: privateEndpoint.?customDnsConfigs + ipConfigurations: privateEndpoint.?ipConfigurations + applicationSecurityGroupResourceIds: privateEndpoint.?applicationSecurityGroupResourceIds + customNetworkInterfaceName: privateEndpoint.?customNetworkInterfaceName } }] @@ -316,3 +316,58 @@ type roleAssignmentType = { @description('Optional. The Resource Id of the delegated managed identity resource.') delegatedManagedIdentityResourceId: string? }[]? + +type privateEndpointType = { + @description('Optional. The name of the private endpoint.') + name: string? + + @description('Optional. The location to deploy the private endpoint to.') + location: string? + + @description('Optional. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob".') + service: string? + + @description('Required. Resource ID of the subnet where the endpoint needs to be created.') + subnetResourceId: string + + @description('Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided.') + privateDnsZoneGroupName: string? + + @description('Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones.') + privateDnsZoneResourceIds: string[]? + + @description('Optional. Custom DNS configurations.') + customDnsConfigs: { + fqdn: string? + ipAddresses: string[] + }[]? + + @description('Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints.') + ipConfigurations: { + name: string + groupId: string + memberName: string + privateIpAddress: string + }[]? + + @description('Optional. Application security groups in which the private endpoint IP configuration is included.') + applicationSecurityGroupResourceIds: string[]? + + @description('Optional. The custom name of the network interface attached to the private endpoint.') + customNetworkInterfaceName: string? + + @description('Optional. Specify the type of lock.') + lock: lockType + + @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\'.') + roleAssignments: roleAssignmentType + + @description('Optional. Tags to be applied on all resources/resource groups in this deployment.') + tags: object? + + @description('Optional. Manual PrivateLink Service Connections.') + manualPrivateLinkServiceConnections: array? + + @description('Optional. Enable/Disable usage telemetry for module.') + enableTelemetry: bool? +}[]? diff --git a/modules/search/search-service/main.json b/modules/search/search-service/main.json index 5b2de842bb..3297608f20 100644 --- a/modules/search/search-service/main.json +++ b/modules/search/search-service/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.22.6.54827", - "templateHash": "5302357571104017921" + "templateHash": "9705671416118103227" }, "name": "Search Services", "description": "This module deploys a Search Service.", @@ -103,6 +103,155 @@ } }, "nullable": true + }, + "privateEndpointType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private endpoint." + } + }, + "location": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The location to deploy the private endpoint to." + } + }, + "service": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The service (sub-) type to deploy the private endpoint for. For example \"vault\" or \"blob\"." + } + }, + "subnetResourceId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the subnet where the endpoint needs to be created." + } + }, + "privateDnsZoneGroupName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided." + } + }, + "privateDnsZoneResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones." + } + }, + "customDnsConfigs": { + "type": "array", + "items": { + "type": "object", + "properties": { + "fqdn": { + "type": "string", + "nullable": true + }, + "ipAddresses": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. Custom DNS configurations." + } + }, + "ipConfigurations": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "groupId": { + "type": "string" + }, + "memberName": { + "type": "string" + }, + "privateIpAddress": { + "type": "string" + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints." + } + }, + "applicationSecurityGroupResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. Application security groups in which the private endpoint IP configuration is included." + } + }, + "customNetworkInterfaceName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The custom name of the network interface attached to the private endpoint." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "metadata": { + "description": "Optional. Specify the type of lock." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "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'." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags to be applied on all resources/resource groups in this deployment." + } + }, + "manualPrivateLinkServiceConnections": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. Manual PrivateLink Service Connections." + } + }, + "enableTelemetry": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + } + } + }, + "nullable": true } }, "parameters": { @@ -186,8 +335,7 @@ } }, "privateEndpoints": { - "type": "array", - "defaultValue": [], + "$ref": "#/definitions/privateEndpointType", "metadata": { "description": "Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible." } @@ -444,7 +592,7 @@ "searchService_privateEndpoints": { "copy": { "name": "searchService_privateEndpoints", - "count": "[length(parameters('privateEndpoints'))]" + "count": "[length(coalesce(parameters('privateEndpoints'), createArray()))]" }, "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", @@ -457,32 +605,54 @@ "parameters": { "groupIds": { "value": [ - "[parameters('privateEndpoints')[copyIndex()].service]" + "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'searchService')]" ] }, - "name": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'name'), createObject('value', parameters('privateEndpoints')[copyIndex()].name), createObject('value', format('pe-{0}-{1}-{2}', last(split(resourceId('Microsoft.Search/searchServices', parameters('name')), '/')), parameters('privateEndpoints')[copyIndex()].service, copyIndex())))]", + "name": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'name'), format('pep-{0}-{1}-{2}', last(split(resourceId('Microsoft.Search/searchServices', parameters('name')), '/')), coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'searchService'), copyIndex()))]" + }, "serviceResourceId": { "value": "[resourceId('Microsoft.Search/searchServices', parameters('name'))]" }, "subnetResourceId": { - "value": "[parameters('privateEndpoints')[copyIndex()].subnetResourceId]" + "value": "[coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId]" }, "enableDefaultTelemetry": { - "value": "[variables('enableReferencedModulesTelemetry')]" + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'enableDefaultTelemetry'), variables('enableReferencedModulesTelemetry'))]" + }, + "location": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'location'), reference(split(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location)]" }, - "location": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'location'), createObject('value', parameters('privateEndpoints')[copyIndex()].location), createObject('value', reference(split(parameters('privateEndpoints')[copyIndex()].subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location))]", "lock": { - "value": "[coalesce(tryGet(parameters('privateEndpoints')[copyIndex()], 'lock'), parameters('lock'))]" + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'lock'), parameters('lock'))]" + }, + "privateDnsZoneGroupName": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneGroupName')]" + }, + "privateDnsZoneResourceIds": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneResourceIds')]" + }, + "roleAssignments": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'roleAssignments')]" }, - "privateDnsZoneGroupName": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'privateDnsZoneGroupName'), createObject('value', parameters('privateEndpoints')[copyIndex()].privateDnsZoneGroupName), createObject('value', 'default'))]", - "privateDnsZoneResourceIds": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'privateDnsZoneResourceIds'), createObject('value', parameters('privateEndpoints')[copyIndex()].privateDnsZoneResourceIds), createObject('value', createArray()))]", - "roleAssignments": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'roleAssignments'), createObject('value', parameters('privateEndpoints')[copyIndex()].roleAssignments), createObject('value', createArray()))]", - "tags": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'tags'), createObject('value', parameters('privateEndpoints')[copyIndex()].tags), createObject('value', createObject()))]", - "manualPrivateLinkServiceConnections": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'manualPrivateLinkServiceConnections'), createObject('value', parameters('privateEndpoints')[copyIndex()].manualPrivateLinkServiceConnections), createObject('value', createArray()))]", - "customDnsConfigs": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'customDnsConfigs'), createObject('value', parameters('privateEndpoints')[copyIndex()].customDnsConfigs), createObject('value', createArray()))]", - "ipConfigurations": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'ipConfigurations'), createObject('value', parameters('privateEndpoints')[copyIndex()].ipConfigurations), createObject('value', createArray()))]", - "applicationSecurityGroupResourceIds": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'applicationSecurityGroupResourceIds'), createObject('value', parameters('privateEndpoints')[copyIndex()].applicationSecurityGroupResourceIds), createObject('value', createArray()))]", - "customNetworkInterfaceName": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'customNetworkInterfaceName'), createObject('value', parameters('privateEndpoints')[copyIndex()].customNetworkInterfaceName), createObject('value', ''))]" + "tags": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'tags'), parameters('tags'))]" + }, + "manualPrivateLinkServiceConnections": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'manualPrivateLinkServiceConnections')]" + }, + "customDnsConfigs": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customDnsConfigs')]" + }, + "ipConfigurations": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'ipConfigurations')]" + }, + "applicationSecurityGroupResourceIds": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'applicationSecurityGroupResourceIds')]" + }, + "customNetworkInterfaceName": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customNetworkInterfaceName')]" + } }, "template": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", diff --git a/modules/service-bus/namespace/.test/pe/main.test.bicep b/modules/service-bus/namespace/.test/pe/main.test.bicep index 936692a3b1..90ad3f25ee 100644 --- a/modules/service-bus/namespace/.test/pe/main.test.bicep +++ b/modules/service-bus/namespace/.test/pe/main.test.bicep @@ -53,7 +53,6 @@ module testDeployment '../../main.bicep' = { publicNetworkAccess: 'Disabled' privateEndpoints: [ { - service: 'namespace' subnetResourceId: nestedDependencies.outputs.subnetResourceId privateDnsZoneResourceIds: [ nestedDependencies.outputs.privateDNSZoneResourceId diff --git a/modules/service-bus/namespace/README.md b/modules/service-bus/namespace/README.md index 2aea46cd40..684a6dcac6 100644 --- a/modules/service-bus/namespace/README.md +++ b/modules/service-bus/namespace/README.md @@ -662,7 +662,6 @@ module namespace 'br:bicep/modules/service-bus.namespace:1.0.0' = { privateDnsZoneResourceIds: [ '' ] - service: 'namespace' subnetResourceId: '' tags: { Environment: 'Non-Prod' @@ -708,7 +707,6 @@ module namespace 'br:bicep/modules/service-bus.namespace:1.0.0' = { "privateDnsZoneResourceIds": [ "" ], - "service": "namespace", "subnetResourceId": "", "tags": { "Environment": "Non-Prod", @@ -979,7 +977,168 @@ The number of partitions of a Service Bus namespace. This property is only appli Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible. - Required: No - Type: array -- Default: `[]` + + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`applicationSecurityGroupResourceIds`](#parameter-privateendpointsapplicationsecuritygroupresourceids) | No | array | Optional. Application security groups in which the private endpoint IP configuration is included. | +| [`customDnsConfigs`](#parameter-privateendpointscustomdnsconfigs) | No | array | Optional. Custom DNS configurations. | +| [`customNetworkInterfaceName`](#parameter-privateendpointscustomnetworkinterfacename) | No | string | Optional. The custom name of the network interface attached to the private endpoint. | +| [`enableTelemetry`](#parameter-privateendpointsenabletelemetry) | No | bool | Optional. Enable/Disable usage telemetry for module. | +| [`ipConfigurations`](#parameter-privateendpointsipconfigurations) | No | array | Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints. | +| [`location`](#parameter-privateendpointslocation) | No | string | Optional. The location to deploy the private endpoint to. | +| [`lock`](#parameter-privateendpointslock) | No | object | Optional. Specify the type of lock. | +| [`manualPrivateLinkServiceConnections`](#parameter-privateendpointsmanualprivatelinkserviceconnections) | No | array | Optional. Manual PrivateLink Service Connections. | +| [`name`](#parameter-privateendpointsname) | No | string | Optional. The name of the private endpoint. | +| [`privateDnsZoneGroupName`](#parameter-privateendpointsprivatednszonegroupname) | No | string | Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided. | +| [`privateDnsZoneResourceIds`](#parameter-privateendpointsprivatednszoneresourceids) | No | array | Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones. | +| [`roleAssignments`](#parameter-privateendpointsroleassignments) | No | array | 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'. | +| [`service`](#parameter-privateendpointsservice) | No | string | Optional. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob". | +| [`subnetResourceId`](#parameter-privateendpointssubnetresourceid) | Yes | string | Required. Resource ID of the subnet where the endpoint needs to be created. | +| [`tags`](#parameter-privateendpointstags) | No | object | Optional. Tags to be applied on all resources/resource groups in this deployment. | + +### Parameter: `privateEndpoints.applicationSecurityGroupResourceIds` + +Optional. Application security groups in which the private endpoint IP configuration is included. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.customDnsConfigs` + +Optional. Custom DNS configurations. + +- Required: No +- Type: array + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`fqdn`](#parameter-privateendpointscustomdnsconfigsfqdn) | No | string | | +| [`ipAddresses`](#parameter-privateendpointscustomdnsconfigsipaddresses) | Yes | array | | + +### Parameter: `privateEndpoints.customDnsConfigs.fqdn` +- Required: No +- Type: string + +### Parameter: `privateEndpoints.customDnsConfigs.ipAddresses` +- Required: Yes +- Type: array + + +### Parameter: `privateEndpoints.customNetworkInterfaceName` + +Optional. The custom name of the network interface attached to the private endpoint. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.enableTelemetry` + +Optional. Enable/Disable usage telemetry for module. + +- Required: No +- Type: bool + +### Parameter: `privateEndpoints.ipConfigurations` + +Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints. + +- Required: No +- Type: array + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`groupId`](#parameter-privateendpointsipconfigurationsgroupid) | Yes | string | | +| [`memberName`](#parameter-privateendpointsipconfigurationsmembername) | Yes | string | | +| [`name`](#parameter-privateendpointsipconfigurationsname) | Yes | string | | +| [`privateIpAddress`](#parameter-privateendpointsipconfigurationsprivateipaddress) | Yes | string | | + +### Parameter: `privateEndpoints.ipConfigurations.groupId` +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.ipConfigurations.memberName` +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.ipConfigurations.name` +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.ipConfigurations.privateIpAddress` +- Required: Yes +- Type: string + + +### Parameter: `privateEndpoints.location` + +Optional. The location to deploy the private endpoint to. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.lock` + +Optional. Specify the type of lock. + +- Required: No +- Type: object + +### Parameter: `privateEndpoints.manualPrivateLinkServiceConnections` + +Optional. Manual PrivateLink Service Connections. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.name` + +Optional. The name of the private endpoint. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.privateDnsZoneGroupName` + +Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.privateDnsZoneResourceIds` + +Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.roleAssignments` + +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'. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.service` + +Optional. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob". + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.subnetResourceId` + +Required. Resource ID of the subnet where the endpoint needs to be created. + +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.tags` + +Optional. Tags to be applied on all resources/resource groups in this deployment. + +- Required: No +- Type: object ### Parameter: `publicNetworkAccess` diff --git a/modules/service-bus/namespace/main.bicep b/modules/service-bus/namespace/main.bicep index 86d634ff3c..c819ee7c84 100644 --- a/modules/service-bus/namespace/main.bicep +++ b/modules/service-bus/namespace/main.bicep @@ -97,7 +97,7 @@ param roleAssignments roleAssignmentType param publicNetworkAccess string = '' @description('Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible.') -param privateEndpoints array = [] +param privateEndpoints privateEndpointType @description('Optional. Configure networking options for Premium SKU Service Bus. 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 = {} @@ -380,27 +380,27 @@ resource serviceBusNamespace_diagnosticSettings 'Microsoft.Insights/diagnosticSe scope: serviceBusNamespace } -module serviceBusNamespace_privateEndpoints '../../network/private-endpoint/main.bicep' = [for (privateEndpoint, index) in privateEndpoints: { - name: '${uniqueString(deployment().name, location)}-Namespace-PrivateEndpoint-${index}' +module serviceBusNamespace_privateEndpoints '../../network/private-endpoint/main.bicep' = [for (privateEndpoint, index) in (privateEndpoints ?? []): { + name: '${uniqueString(deployment().name, location)}-serviceBusNamespace-PrivateEndpoint-${index}' params: { groupIds: [ - privateEndpoint.service + privateEndpoint.?service ?? 'namespace' ] - name: contains(privateEndpoint, 'name') ? privateEndpoint.name : 'pe-${last(split(serviceBusNamespace.id, '/'))}-${privateEndpoint.service}-${index}' + name: privateEndpoint.?name ?? 'pep-${last(split(serviceBusNamespace.id, '/'))}-${privateEndpoint.?service ?? 'namespace'}-${index}' serviceResourceId: serviceBusNamespace.id subnetResourceId: privateEndpoint.subnetResourceId - enableDefaultTelemetry: enableReferencedModulesTelemetry - location: contains(privateEndpoint, 'location') ? privateEndpoint.location : reference(split(privateEndpoint.subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location + enableDefaultTelemetry: privateEndpoint.?enableDefaultTelemetry ?? enableReferencedModulesTelemetry + location: privateEndpoint.?location ?? reference(split(privateEndpoint.subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location lock: privateEndpoint.?lock ?? lock - privateDnsZoneGroupName: contains(privateEndpoint, 'privateDnsZoneGroupName') ? privateEndpoint.privateDnsZoneGroupName : 'default' - privateDnsZoneResourceIds: contains(privateEndpoint, 'privateDnsZoneResourceIds') ? privateEndpoint.privateDnsZoneResourceIds : [] - 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 : [] - applicationSecurityGroupResourceIds: contains(privateEndpoint, 'applicationSecurityGroupResourceIds') ? privateEndpoint.applicationSecurityGroupResourceIds : [] - customNetworkInterfaceName: contains(privateEndpoint, 'customNetworkInterfaceName') ? privateEndpoint.customNetworkInterfaceName : '' + privateDnsZoneGroupName: privateEndpoint.?privateDnsZoneGroupName + privateDnsZoneResourceIds: privateEndpoint.?privateDnsZoneResourceIds + roleAssignments: privateEndpoint.?roleAssignments + tags: privateEndpoint.?tags ?? tags + manualPrivateLinkServiceConnections: privateEndpoint.?manualPrivateLinkServiceConnections + customDnsConfigs: privateEndpoint.?customDnsConfigs + ipConfigurations: privateEndpoint.?ipConfigurations + applicationSecurityGroupResourceIds: privateEndpoint.?applicationSecurityGroupResourceIds + customNetworkInterfaceName: privateEndpoint.?customNetworkInterfaceName } }] @@ -467,3 +467,58 @@ type roleAssignmentType = { @description('Optional. The Resource Id of the delegated managed identity resource.') delegatedManagedIdentityResourceId: string? }[]? + +type privateEndpointType = { + @description('Optional. The name of the private endpoint.') + name: string? + + @description('Optional. The location to deploy the private endpoint to.') + location: string? + + @description('Optional. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob".') + service: string? + + @description('Required. Resource ID of the subnet where the endpoint needs to be created.') + subnetResourceId: string + + @description('Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided.') + privateDnsZoneGroupName: string? + + @description('Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones.') + privateDnsZoneResourceIds: string[]? + + @description('Optional. Custom DNS configurations.') + customDnsConfigs: { + fqdn: string? + ipAddresses: string[] + }[]? + + @description('Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints.') + ipConfigurations: { + name: string + groupId: string + memberName: string + privateIpAddress: string + }[]? + + @description('Optional. Application security groups in which the private endpoint IP configuration is included.') + applicationSecurityGroupResourceIds: string[]? + + @description('Optional. The custom name of the network interface attached to the private endpoint.') + customNetworkInterfaceName: string? + + @description('Optional. Specify the type of lock.') + lock: lockType + + @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\'.') + roleAssignments: roleAssignmentType + + @description('Optional. Tags to be applied on all resources/resource groups in this deployment.') + tags: object? + + @description('Optional. Manual PrivateLink Service Connections.') + manualPrivateLinkServiceConnections: array? + + @description('Optional. Enable/Disable usage telemetry for module.') + enableTelemetry: bool? +}[]? diff --git a/modules/service-bus/namespace/main.json b/modules/service-bus/namespace/main.json index bc6b908053..2c000d1410 100644 --- a/modules/service-bus/namespace/main.json +++ b/modules/service-bus/namespace/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.22.6.54827", - "templateHash": "9892377327187040976" + "templateHash": "15092397707699108570" }, "name": "Service Bus Namespaces", "description": "This module deploys a Service Bus Namespace.", @@ -103,6 +103,155 @@ } }, "nullable": true + }, + "privateEndpointType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private endpoint." + } + }, + "location": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The location to deploy the private endpoint to." + } + }, + "service": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The service (sub-) type to deploy the private endpoint for. For example \"vault\" or \"blob\"." + } + }, + "subnetResourceId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the subnet where the endpoint needs to be created." + } + }, + "privateDnsZoneGroupName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided." + } + }, + "privateDnsZoneResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones." + } + }, + "customDnsConfigs": { + "type": "array", + "items": { + "type": "object", + "properties": { + "fqdn": { + "type": "string", + "nullable": true + }, + "ipAddresses": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. Custom DNS configurations." + } + }, + "ipConfigurations": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "groupId": { + "type": "string" + }, + "memberName": { + "type": "string" + }, + "privateIpAddress": { + "type": "string" + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints." + } + }, + "applicationSecurityGroupResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. Application security groups in which the private endpoint IP configuration is included." + } + }, + "customNetworkInterfaceName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The custom name of the network interface attached to the private endpoint." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "metadata": { + "description": "Optional. Specify the type of lock." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "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'." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags to be applied on all resources/resource groups in this deployment." + } + }, + "manualPrivateLinkServiceConnections": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. Manual PrivateLink Service Connections." + } + }, + "enableTelemetry": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + } + } + }, + "nullable": true } }, "parameters": { @@ -278,8 +427,7 @@ } }, "privateEndpoints": { - "type": "array", - "defaultValue": [], + "$ref": "#/definitions/privateEndpointType", "metadata": { "description": "Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible." } @@ -2217,11 +2365,11 @@ "serviceBusNamespace_privateEndpoints": { "copy": { "name": "serviceBusNamespace_privateEndpoints", - "count": "[length(parameters('privateEndpoints'))]" + "count": "[length(coalesce(parameters('privateEndpoints'), createArray()))]" }, "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", - "name": "[format('{0}-Namespace-PrivateEndpoint-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "name": "[format('{0}-serviceBusNamespace-PrivateEndpoint-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", "properties": { "expressionEvaluationOptions": { "scope": "inner" @@ -2230,32 +2378,54 @@ "parameters": { "groupIds": { "value": [ - "[parameters('privateEndpoints')[copyIndex()].service]" + "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'namespace')]" ] }, - "name": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'name'), createObject('value', parameters('privateEndpoints')[copyIndex()].name), createObject('value', format('pe-{0}-{1}-{2}', last(split(resourceId('Microsoft.ServiceBus/namespaces', parameters('name')), '/')), parameters('privateEndpoints')[copyIndex()].service, copyIndex())))]", + "name": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'name'), format('pep-{0}-{1}-{2}', last(split(resourceId('Microsoft.ServiceBus/namespaces', parameters('name')), '/')), coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'namespace'), copyIndex()))]" + }, "serviceResourceId": { "value": "[resourceId('Microsoft.ServiceBus/namespaces', parameters('name'))]" }, "subnetResourceId": { - "value": "[parameters('privateEndpoints')[copyIndex()].subnetResourceId]" + "value": "[coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId]" }, "enableDefaultTelemetry": { - "value": "[variables('enableReferencedModulesTelemetry')]" + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'enableDefaultTelemetry'), variables('enableReferencedModulesTelemetry'))]" + }, + "location": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'location'), reference(split(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location)]" }, - "location": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'location'), createObject('value', parameters('privateEndpoints')[copyIndex()].location), createObject('value', reference(split(parameters('privateEndpoints')[copyIndex()].subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location))]", "lock": { - "value": "[coalesce(tryGet(parameters('privateEndpoints')[copyIndex()], 'lock'), parameters('lock'))]" - }, - "privateDnsZoneGroupName": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'privateDnsZoneGroupName'), createObject('value', parameters('privateEndpoints')[copyIndex()].privateDnsZoneGroupName), createObject('value', 'default'))]", - "privateDnsZoneResourceIds": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'privateDnsZoneResourceIds'), createObject('value', parameters('privateEndpoints')[copyIndex()].privateDnsZoneResourceIds), createObject('value', createArray()))]", - "roleAssignments": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'roleAssignments'), createObject('value', parameters('privateEndpoints')[copyIndex()].roleAssignments), createObject('value', createArray()))]", - "tags": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'tags'), createObject('value', parameters('privateEndpoints')[copyIndex()].tags), createObject('value', createObject()))]", - "manualPrivateLinkServiceConnections": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'manualPrivateLinkServiceConnections'), createObject('value', parameters('privateEndpoints')[copyIndex()].manualPrivateLinkServiceConnections), createObject('value', createArray()))]", - "customDnsConfigs": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'customDnsConfigs'), createObject('value', parameters('privateEndpoints')[copyIndex()].customDnsConfigs), createObject('value', createArray()))]", - "ipConfigurations": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'ipConfigurations'), createObject('value', parameters('privateEndpoints')[copyIndex()].ipConfigurations), createObject('value', createArray()))]", - "applicationSecurityGroupResourceIds": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'applicationSecurityGroupResourceIds'), createObject('value', parameters('privateEndpoints')[copyIndex()].applicationSecurityGroupResourceIds), createObject('value', createArray()))]", - "customNetworkInterfaceName": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'customNetworkInterfaceName'), createObject('value', parameters('privateEndpoints')[copyIndex()].customNetworkInterfaceName), createObject('value', ''))]" + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'lock'), parameters('lock'))]" + }, + "privateDnsZoneGroupName": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneGroupName')]" + }, + "privateDnsZoneResourceIds": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneResourceIds')]" + }, + "roleAssignments": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'roleAssignments')]" + }, + "tags": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'tags'), parameters('tags'))]" + }, + "manualPrivateLinkServiceConnections": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'manualPrivateLinkServiceConnections')]" + }, + "customDnsConfigs": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customDnsConfigs')]" + }, + "ipConfigurations": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'ipConfigurations')]" + }, + "applicationSecurityGroupResourceIds": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'applicationSecurityGroupResourceIds')]" + }, + "customNetworkInterfaceName": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customNetworkInterfaceName')]" + } }, "template": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", diff --git a/modules/signal-r-service/signal-r/.test/common/main.test.bicep b/modules/signal-r-service/signal-r/.test/common/main.test.bicep index 1b86eadafe..ce4fae589c 100644 --- a/modules/signal-r-service/signal-r/.test/common/main.test.bicep +++ b/modules/signal-r-service/signal-r/.test/common/main.test.bicep @@ -87,11 +87,8 @@ module testDeployment '../../main.bicep' = { privateEndpoints: [ { privateDnsZoneResourceIds: [ - nestedDependencies.outputs.privateDNSZoneResourceId - ] - service: 'signalr' subnetResourceId: nestedDependencies.outputs.subnetResourceId tags: { 'hidden-title': 'This is visible in the resource name' diff --git a/modules/signal-r-service/signal-r/README.md b/modules/signal-r-service/signal-r/README.md index c2662cc6c7..ff3c273afd 100644 --- a/modules/signal-r-service/signal-r/README.md +++ b/modules/signal-r-service/signal-r/README.md @@ -83,7 +83,6 @@ module signalR 'br:bicep/modules/signal-r-service.signal-r:1.0.0' = { privateDnsZoneResourceIds: [ '' ] - service: 'signalr' subnetResourceId: '' tags: { Environment: 'Non-Prod' @@ -184,7 +183,6 @@ module signalR 'br:bicep/modules/signal-r-service.signal-r:1.0.0' = { "privateDnsZoneResourceIds": [ "" ], - "service": "signalr", "subnetResourceId": "", "tags": { "Environment": "Non-Prod", @@ -423,7 +421,168 @@ Networks ACLs, this value contains IPs to allow and/or Subnet information. Can o Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible. - Required: No - Type: array -- Default: `[]` + + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`applicationSecurityGroupResourceIds`](#parameter-privateendpointsapplicationsecuritygroupresourceids) | No | array | Optional. Application security groups in which the private endpoint IP configuration is included. | +| [`customDnsConfigs`](#parameter-privateendpointscustomdnsconfigs) | No | array | Optional. Custom DNS configurations. | +| [`customNetworkInterfaceName`](#parameter-privateendpointscustomnetworkinterfacename) | No | string | Optional. The custom name of the network interface attached to the private endpoint. | +| [`enableTelemetry`](#parameter-privateendpointsenabletelemetry) | No | bool | Optional. Enable/Disable usage telemetry for module. | +| [`ipConfigurations`](#parameter-privateendpointsipconfigurations) | No | array | Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints. | +| [`location`](#parameter-privateendpointslocation) | No | string | Optional. The location to deploy the private endpoint to. | +| [`lock`](#parameter-privateendpointslock) | No | object | Optional. Specify the type of lock. | +| [`manualPrivateLinkServiceConnections`](#parameter-privateendpointsmanualprivatelinkserviceconnections) | No | array | Optional. Manual PrivateLink Service Connections. | +| [`name`](#parameter-privateendpointsname) | No | string | Optional. The name of the private endpoint. | +| [`privateDnsZoneGroupName`](#parameter-privateendpointsprivatednszonegroupname) | No | string | Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided. | +| [`privateDnsZoneResourceIds`](#parameter-privateendpointsprivatednszoneresourceids) | No | array | Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones. | +| [`roleAssignments`](#parameter-privateendpointsroleassignments) | No | array | 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'. | +| [`service`](#parameter-privateendpointsservice) | No | string | Optional. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob". | +| [`subnetResourceId`](#parameter-privateendpointssubnetresourceid) | Yes | string | Required. Resource ID of the subnet where the endpoint needs to be created. | +| [`tags`](#parameter-privateendpointstags) | No | object | Optional. Tags to be applied on all resources/resource groups in this deployment. | + +### Parameter: `privateEndpoints.applicationSecurityGroupResourceIds` + +Optional. Application security groups in which the private endpoint IP configuration is included. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.customDnsConfigs` + +Optional. Custom DNS configurations. + +- Required: No +- Type: array + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`fqdn`](#parameter-privateendpointscustomdnsconfigsfqdn) | No | string | | +| [`ipAddresses`](#parameter-privateendpointscustomdnsconfigsipaddresses) | Yes | array | | + +### Parameter: `privateEndpoints.customDnsConfigs.fqdn` +- Required: No +- Type: string + +### Parameter: `privateEndpoints.customDnsConfigs.ipAddresses` +- Required: Yes +- Type: array + + +### Parameter: `privateEndpoints.customNetworkInterfaceName` + +Optional. The custom name of the network interface attached to the private endpoint. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.enableTelemetry` + +Optional. Enable/Disable usage telemetry for module. + +- Required: No +- Type: bool + +### Parameter: `privateEndpoints.ipConfigurations` + +Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints. + +- Required: No +- Type: array + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`groupId`](#parameter-privateendpointsipconfigurationsgroupid) | Yes | string | | +| [`memberName`](#parameter-privateendpointsipconfigurationsmembername) | Yes | string | | +| [`name`](#parameter-privateendpointsipconfigurationsname) | Yes | string | | +| [`privateIpAddress`](#parameter-privateendpointsipconfigurationsprivateipaddress) | Yes | string | | + +### Parameter: `privateEndpoints.ipConfigurations.groupId` +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.ipConfigurations.memberName` +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.ipConfigurations.name` +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.ipConfigurations.privateIpAddress` +- Required: Yes +- Type: string + + +### Parameter: `privateEndpoints.location` + +Optional. The location to deploy the private endpoint to. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.lock` + +Optional. Specify the type of lock. + +- Required: No +- Type: object + +### Parameter: `privateEndpoints.manualPrivateLinkServiceConnections` + +Optional. Manual PrivateLink Service Connections. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.name` + +Optional. The name of the private endpoint. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.privateDnsZoneGroupName` + +Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.privateDnsZoneResourceIds` + +Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.roleAssignments` + +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'. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.service` + +Optional. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob". + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.subnetResourceId` + +Required. Resource ID of the subnet where the endpoint needs to be created. + +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.tags` + +Optional. Tags to be applied on all resources/resource groups in this deployment. + +- Required: No +- Type: object ### Parameter: `publicNetworkAccess` diff --git a/modules/signal-r-service/signal-r/main.bicep b/modules/signal-r-service/signal-r/main.bicep index 2888dcbf64..385295b8f2 100644 --- a/modules/signal-r-service/signal-r/main.bicep +++ b/modules/signal-r-service/signal-r/main.bicep @@ -90,7 +90,7 @@ param clientCertEnabled bool = false param upstreamTemplatesToEnable array = [] @description('Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible.') -param privateEndpoints array = [] +param privateEndpoints privateEndpointType @description('Optional. The lock settings of the service.') param lock lockType @@ -101,6 +101,8 @@ param roleAssignments roleAssignmentType @description('Optional. Enable telemetry via a Globally Unique Identifier (GUID).') param enableDefaultTelemetry bool = true +var enableReferencedModulesTelemetry = false + var liveTraceCatagories = [for configuration in liveTraceCatagoriesToEnable: { name: configuration enabled: 'true' @@ -173,26 +175,27 @@ resource signalR 'Microsoft.SignalRService/signalR@2022-02-01' = { } } -module signalR_privateEndpoints '../../network/private-endpoint/main.bicep' = [for (privateEndpoint, index) in privateEndpoints: { - name: '${uniqueString(deployment().name, location)}-SignalR-PrivateEndpoint-${index}' +module signalR_privateEndpoints '../../network/private-endpoint/main.bicep' = [for (privateEndpoint, index) in (privateEndpoints ?? []): { + name: '${uniqueString(deployment().name, location)}-signalR-PrivateEndpoint-${index}' params: { groupIds: [ - privateEndpoint.service + privateEndpoint.?service ?? 'signalr' ] - name: contains(privateEndpoint, 'name') ? privateEndpoint.name : 'pe-${last(split(signalR.id, '/'))}-${privateEndpoint.service}-${index}' + name: privateEndpoint.?name ?? 'pep-${last(split(signalR.id, '/'))}-${privateEndpoint.?service ?? 'signalr'}-${index}' serviceResourceId: signalR.id subnetResourceId: privateEndpoint.subnetResourceId - location: contains(privateEndpoint, 'location') ? privateEndpoint.location : reference(split(privateEndpoint.subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location + enableDefaultTelemetry: privateEndpoint.?enableDefaultTelemetry ?? enableReferencedModulesTelemetry + location: privateEndpoint.?location ?? reference(split(privateEndpoint.subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location lock: privateEndpoint.?lock ?? lock - privateDnsZoneGroupName: contains(privateEndpoint, 'privateDnsZoneGroupName') ? privateEndpoint.privateDnsZoneGroupName : 'default' - privateDnsZoneResourceIds: contains(privateEndpoint, 'privateDnsZoneResourceIds') ? privateEndpoint.privateDnsZoneResourceIds : [] - 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 : [] - applicationSecurityGroupResourceIds: contains(privateEndpoint, 'applicationSecurityGroupResourceIds') ? privateEndpoint.applicationSecurityGroupResourceIds : [] - customNetworkInterfaceName: contains(privateEndpoint, 'customNetworkInterfaceName') ? privateEndpoint.customNetworkInterfaceName : '' + privateDnsZoneGroupName: privateEndpoint.?privateDnsZoneGroupName + privateDnsZoneResourceIds: privateEndpoint.?privateDnsZoneResourceIds + roleAssignments: privateEndpoint.?roleAssignments + tags: privateEndpoint.?tags ?? tags + manualPrivateLinkServiceConnections: privateEndpoint.?manualPrivateLinkServiceConnections + customDnsConfigs: privateEndpoint.?customDnsConfigs + ipConfigurations: privateEndpoint.?ipConfigurations + applicationSecurityGroupResourceIds: privateEndpoint.?applicationSecurityGroupResourceIds + customNetworkInterfaceName: privateEndpoint.?customNetworkInterfaceName } }] @@ -265,3 +268,58 @@ type roleAssignmentType = { @description('Optional. The Resource Id of the delegated managed identity resource.') delegatedManagedIdentityResourceId: string? }[]? + +type privateEndpointType = { + @description('Optional. The name of the private endpoint.') + name: string? + + @description('Optional. The location to deploy the private endpoint to.') + location: string? + + @description('Optional. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob".') + service: string? + + @description('Required. Resource ID of the subnet where the endpoint needs to be created.') + subnetResourceId: string + + @description('Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided.') + privateDnsZoneGroupName: string? + + @description('Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones.') + privateDnsZoneResourceIds: string[]? + + @description('Optional. Custom DNS configurations.') + customDnsConfigs: { + fqdn: string? + ipAddresses: string[] + }[]? + + @description('Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints.') + ipConfigurations: { + name: string + groupId: string + memberName: string + privateIpAddress: string + }[]? + + @description('Optional. Application security groups in which the private endpoint IP configuration is included.') + applicationSecurityGroupResourceIds: string[]? + + @description('Optional. The custom name of the network interface attached to the private endpoint.') + customNetworkInterfaceName: string? + + @description('Optional. Specify the type of lock.') + lock: lockType + + @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\'.') + roleAssignments: roleAssignmentType + + @description('Optional. Tags to be applied on all resources/resource groups in this deployment.') + tags: object? + + @description('Optional. Manual PrivateLink Service Connections.') + manualPrivateLinkServiceConnections: array? + + @description('Optional. Enable/Disable usage telemetry for module.') + enableTelemetry: bool? +}[]? diff --git a/modules/signal-r-service/signal-r/main.json b/modules/signal-r-service/signal-r/main.json index 7f28716ce7..bf43fef7a7 100644 --- a/modules/signal-r-service/signal-r/main.json +++ b/modules/signal-r-service/signal-r/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.22.6.54827", - "templateHash": "15253886392220203228" + "templateHash": "4290982066037624920" }, "name": "SignalR Service SignalR", "description": "This module deploys a SignalR Service SignalR.", @@ -103,6 +103,155 @@ } }, "nullable": true + }, + "privateEndpointType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private endpoint." + } + }, + "location": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The location to deploy the private endpoint to." + } + }, + "service": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The service (sub-) type to deploy the private endpoint for. For example \"vault\" or \"blob\"." + } + }, + "subnetResourceId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the subnet where the endpoint needs to be created." + } + }, + "privateDnsZoneGroupName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided." + } + }, + "privateDnsZoneResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones." + } + }, + "customDnsConfigs": { + "type": "array", + "items": { + "type": "object", + "properties": { + "fqdn": { + "type": "string", + "nullable": true + }, + "ipAddresses": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. Custom DNS configurations." + } + }, + "ipConfigurations": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "groupId": { + "type": "string" + }, + "memberName": { + "type": "string" + }, + "privateIpAddress": { + "type": "string" + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints." + } + }, + "applicationSecurityGroupResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. Application security groups in which the private endpoint IP configuration is included." + } + }, + "customNetworkInterfaceName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The custom name of the network interface attached to the private endpoint." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "metadata": { + "description": "Optional. Specify the type of lock." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "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'." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags to be applied on all resources/resource groups in this deployment." + } + }, + "manualPrivateLinkServiceConnections": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. Manual PrivateLink Service Connections." + } + }, + "enableTelemetry": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + } + } + }, + "nullable": true } }, "parameters": { @@ -257,8 +406,7 @@ } }, "privateEndpoints": { - "type": "array", - "defaultValue": [], + "$ref": "#/definitions/privateEndpointType", "metadata": { "description": "Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible." } @@ -302,6 +450,7 @@ } } ], + "enableReferencedModulesTelemetry": false, "builtInRoleNames": { "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]", @@ -403,11 +552,11 @@ "signalR_privateEndpoints": { "copy": { "name": "signalR_privateEndpoints", - "count": "[length(parameters('privateEndpoints'))]" + "count": "[length(coalesce(parameters('privateEndpoints'), createArray()))]" }, "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", - "name": "[format('{0}-SignalR-PrivateEndpoint-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "name": "[format('{0}-signalR-PrivateEndpoint-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", "properties": { "expressionEvaluationOptions": { "scope": "inner" @@ -416,29 +565,54 @@ "parameters": { "groupIds": { "value": [ - "[parameters('privateEndpoints')[copyIndex()].service]" + "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'signalr')]" ] }, - "name": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'name'), createObject('value', parameters('privateEndpoints')[copyIndex()].name), createObject('value', format('pe-{0}-{1}-{2}', last(split(resourceId('Microsoft.SignalRService/signalR', parameters('name')), '/')), parameters('privateEndpoints')[copyIndex()].service, copyIndex())))]", + "name": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'name'), format('pep-{0}-{1}-{2}', last(split(resourceId('Microsoft.SignalRService/signalR', parameters('name')), '/')), coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'signalr'), copyIndex()))]" + }, "serviceResourceId": { "value": "[resourceId('Microsoft.SignalRService/signalR', parameters('name'))]" }, "subnetResourceId": { - "value": "[parameters('privateEndpoints')[copyIndex()].subnetResourceId]" + "value": "[coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId]" + }, + "enableDefaultTelemetry": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'enableDefaultTelemetry'), variables('enableReferencedModulesTelemetry'))]" + }, + "location": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'location'), reference(split(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location)]" }, - "location": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'location'), createObject('value', parameters('privateEndpoints')[copyIndex()].location), createObject('value', reference(split(parameters('privateEndpoints')[copyIndex()].subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location))]", "lock": { - "value": "[coalesce(tryGet(parameters('privateEndpoints')[copyIndex()], 'lock'), parameters('lock'))]" - }, - "privateDnsZoneGroupName": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'privateDnsZoneGroupName'), createObject('value', parameters('privateEndpoints')[copyIndex()].privateDnsZoneGroupName), createObject('value', 'default'))]", - "privateDnsZoneResourceIds": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'privateDnsZoneResourceIds'), createObject('value', parameters('privateEndpoints')[copyIndex()].privateDnsZoneResourceIds), createObject('value', createArray()))]", - "roleAssignments": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'roleAssignments'), createObject('value', parameters('privateEndpoints')[copyIndex()].roleAssignments), createObject('value', createArray()))]", - "tags": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'tags'), createObject('value', parameters('privateEndpoints')[copyIndex()].tags), createObject('value', createObject()))]", - "manualPrivateLinkServiceConnections": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'manualPrivateLinkServiceConnections'), createObject('value', parameters('privateEndpoints')[copyIndex()].manualPrivateLinkServiceConnections), createObject('value', createArray()))]", - "customDnsConfigs": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'customDnsConfigs'), createObject('value', parameters('privateEndpoints')[copyIndex()].customDnsConfigs), createObject('value', createArray()))]", - "ipConfigurations": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'ipConfigurations'), createObject('value', parameters('privateEndpoints')[copyIndex()].ipConfigurations), createObject('value', createArray()))]", - "applicationSecurityGroupResourceIds": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'applicationSecurityGroupResourceIds'), createObject('value', parameters('privateEndpoints')[copyIndex()].applicationSecurityGroupResourceIds), createObject('value', createArray()))]", - "customNetworkInterfaceName": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'customNetworkInterfaceName'), createObject('value', parameters('privateEndpoints')[copyIndex()].customNetworkInterfaceName), createObject('value', ''))]" + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'lock'), parameters('lock'))]" + }, + "privateDnsZoneGroupName": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneGroupName')]" + }, + "privateDnsZoneResourceIds": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneResourceIds')]" + }, + "roleAssignments": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'roleAssignments')]" + }, + "tags": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'tags'), parameters('tags'))]" + }, + "manualPrivateLinkServiceConnections": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'manualPrivateLinkServiceConnections')]" + }, + "customDnsConfigs": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customDnsConfigs')]" + }, + "ipConfigurations": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'ipConfigurations')]" + }, + "applicationSecurityGroupResourceIds": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'applicationSecurityGroupResourceIds')]" + }, + "customNetworkInterfaceName": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customNetworkInterfaceName')]" + } }, "template": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", diff --git a/modules/signal-r-service/web-pub-sub/.test/common/main.test.bicep b/modules/signal-r-service/web-pub-sub/.test/common/main.test.bicep index 0993c69ff6..93a4cde2c8 100644 --- a/modules/signal-r-service/web-pub-sub/.test/common/main.test.bicep +++ b/modules/signal-r-service/web-pub-sub/.test/common/main.test.bicep @@ -85,9 +85,7 @@ module testDeployment '../../main.bicep' = { privateEndpoints: [ { privateDnsZoneResourceIds: [ - nestedDependencies.outputs.privateDNSZoneResourceId - ] service: 'webpubsub' subnetResourceId: nestedDependencies.outputs.subnetResourceId diff --git a/modules/signal-r-service/web-pub-sub/.test/pe/main.test.bicep b/modules/signal-r-service/web-pub-sub/.test/pe/main.test.bicep index cff16d9528..7a565e6ea8 100644 --- a/modules/signal-r-service/web-pub-sub/.test/pe/main.test.bicep +++ b/modules/signal-r-service/web-pub-sub/.test/pe/main.test.bicep @@ -52,11 +52,8 @@ module testDeployment '../../main.bicep' = { privateEndpoints: [ { privateDnsZoneResourceIds: [ - nestedDependencies.outputs.privateDNSZoneResourceId - ] - service: 'webpubsub' subnetResourceId: nestedDependencies.outputs.subnetResourceId tags: { 'hidden-title': 'This is visible in the resource name' diff --git a/modules/signal-r-service/web-pub-sub/README.md b/modules/signal-r-service/web-pub-sub/README.md index 4858ab8413..a1f443b2f3 100644 --- a/modules/signal-r-service/web-pub-sub/README.md +++ b/modules/signal-r-service/web-pub-sub/README.md @@ -293,7 +293,6 @@ module webPubSub 'br:bicep/modules/signal-r-service.web-pub-sub:1.0.0' = { privateDnsZoneResourceIds: [ '' ] - service: 'webpubsub' subnetResourceId: '' tags: { Environment: 'Non-Prod' @@ -338,7 +337,6 @@ module webPubSub 'br:bicep/modules/signal-r-service.web-pub-sub:1.0.0' = { "privateDnsZoneResourceIds": [ "" ], - "service": "webpubsub", "subnetResourceId": "", "tags": { "Environment": "Non-Prod", @@ -482,7 +480,168 @@ Networks ACLs, this value contains IPs to allow and/or Subnet information. Can o Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible. - Required: No - Type: array -- Default: `[]` + + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`applicationSecurityGroupResourceIds`](#parameter-privateendpointsapplicationsecuritygroupresourceids) | No | array | Optional. Application security groups in which the private endpoint IP configuration is included. | +| [`customDnsConfigs`](#parameter-privateendpointscustomdnsconfigs) | No | array | Optional. Custom DNS configurations. | +| [`customNetworkInterfaceName`](#parameter-privateendpointscustomnetworkinterfacename) | No | string | Optional. The custom name of the network interface attached to the private endpoint. | +| [`enableTelemetry`](#parameter-privateendpointsenabletelemetry) | No | bool | Optional. Enable/Disable usage telemetry for module. | +| [`ipConfigurations`](#parameter-privateendpointsipconfigurations) | No | array | Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints. | +| [`location`](#parameter-privateendpointslocation) | No | string | Optional. The location to deploy the private endpoint to. | +| [`lock`](#parameter-privateendpointslock) | No | object | Optional. Specify the type of lock. | +| [`manualPrivateLinkServiceConnections`](#parameter-privateendpointsmanualprivatelinkserviceconnections) | No | array | Optional. Manual PrivateLink Service Connections. | +| [`name`](#parameter-privateendpointsname) | No | string | Optional. The name of the private endpoint. | +| [`privateDnsZoneGroupName`](#parameter-privateendpointsprivatednszonegroupname) | No | string | Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided. | +| [`privateDnsZoneResourceIds`](#parameter-privateendpointsprivatednszoneresourceids) | No | array | Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones. | +| [`roleAssignments`](#parameter-privateendpointsroleassignments) | No | array | 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'. | +| [`service`](#parameter-privateendpointsservice) | No | string | Optional. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob". | +| [`subnetResourceId`](#parameter-privateendpointssubnetresourceid) | Yes | string | Required. Resource ID of the subnet where the endpoint needs to be created. | +| [`tags`](#parameter-privateendpointstags) | No | object | Optional. Tags to be applied on all resources/resource groups in this deployment. | + +### Parameter: `privateEndpoints.applicationSecurityGroupResourceIds` + +Optional. Application security groups in which the private endpoint IP configuration is included. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.customDnsConfigs` + +Optional. Custom DNS configurations. + +- Required: No +- Type: array + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`fqdn`](#parameter-privateendpointscustomdnsconfigsfqdn) | No | string | | +| [`ipAddresses`](#parameter-privateendpointscustomdnsconfigsipaddresses) | Yes | array | | + +### Parameter: `privateEndpoints.customDnsConfigs.fqdn` +- Required: No +- Type: string + +### Parameter: `privateEndpoints.customDnsConfigs.ipAddresses` +- Required: Yes +- Type: array + + +### Parameter: `privateEndpoints.customNetworkInterfaceName` + +Optional. The custom name of the network interface attached to the private endpoint. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.enableTelemetry` + +Optional. Enable/Disable usage telemetry for module. + +- Required: No +- Type: bool + +### Parameter: `privateEndpoints.ipConfigurations` + +Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints. + +- Required: No +- Type: array + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`groupId`](#parameter-privateendpointsipconfigurationsgroupid) | Yes | string | | +| [`memberName`](#parameter-privateendpointsipconfigurationsmembername) | Yes | string | | +| [`name`](#parameter-privateendpointsipconfigurationsname) | Yes | string | | +| [`privateIpAddress`](#parameter-privateendpointsipconfigurationsprivateipaddress) | Yes | string | | + +### Parameter: `privateEndpoints.ipConfigurations.groupId` +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.ipConfigurations.memberName` +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.ipConfigurations.name` +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.ipConfigurations.privateIpAddress` +- Required: Yes +- Type: string + + +### Parameter: `privateEndpoints.location` + +Optional. The location to deploy the private endpoint to. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.lock` + +Optional. Specify the type of lock. + +- Required: No +- Type: object + +### Parameter: `privateEndpoints.manualPrivateLinkServiceConnections` + +Optional. Manual PrivateLink Service Connections. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.name` + +Optional. The name of the private endpoint. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.privateDnsZoneGroupName` + +Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.privateDnsZoneResourceIds` + +Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.roleAssignments` + +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'. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.service` + +Optional. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob". + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.subnetResourceId` + +Required. Resource ID of the subnet where the endpoint needs to be created. + +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.tags` + +Optional. Tags to be applied on all resources/resource groups in this deployment. + +- Required: No +- Type: object ### Parameter: `publicNetworkAccess` diff --git a/modules/signal-r-service/web-pub-sub/main.bicep b/modules/signal-r-service/web-pub-sub/main.bicep index 8bd0b5101b..49fdc78208 100644 --- a/modules/signal-r-service/web-pub-sub/main.bicep +++ b/modules/signal-r-service/web-pub-sub/main.bicep @@ -9,7 +9,7 @@ param location string = resourceGroup().location param name string @description('Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible.') -param privateEndpoints array = [] +param privateEndpoints privateEndpointType @description('Optional. The lock settings of the service.') param lock lockType @@ -76,6 +76,8 @@ var resourceLogConfiguration = [for configuration in resourceLogConfigurationsTo var identityType = systemAssignedIdentity ? 'SystemAssigned' : !empty(userAssignedIdentities) ? 'UserAssigned' : 'None' +var enableReferencedModulesTelemetry = false + var identity = { type: identityType userAssignedIdentities: !empty(userAssignedIdentities) ? userAssignedIdentities : null @@ -133,26 +135,27 @@ resource webPubSub 'Microsoft.SignalRService/webPubSub@2021-10-01' = { } } -module webPubSub_privateEndpoints '../../network/private-endpoint/main.bicep' = [for (privateEndpoint, index) in privateEndpoints: { - name: '${uniqueString(deployment().name, location)}-WebPubSub-PrivateEndpoint-${index}' +module webPubSub_privateEndpoints '../../network/private-endpoint/main.bicep' = [for (privateEndpoint, index) in (privateEndpoints ?? []): { + name: '${uniqueString(deployment().name, location)}-webPubSub-PrivateEndpoint-${index}' params: { groupIds: [ - privateEndpoint.service + privateEndpoint.?service ?? 'webpubsub' ] - name: contains(privateEndpoint, 'name') ? privateEndpoint.name : 'pe-${last(split(webPubSub.id, '/'))}-${privateEndpoint.service}-${index}' + name: privateEndpoint.?name ?? 'pep-${last(split(webPubSub.id, '/'))}-${privateEndpoint.?service ?? 'webpubsub'}-${index}' serviceResourceId: webPubSub.id subnetResourceId: privateEndpoint.subnetResourceId - location: contains(privateEndpoint, 'location') ? privateEndpoint.location : reference(split(privateEndpoint.subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location + enableDefaultTelemetry: privateEndpoint.?enableDefaultTelemetry ?? enableReferencedModulesTelemetry + location: privateEndpoint.?location ?? reference(split(privateEndpoint.subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location lock: privateEndpoint.?lock ?? lock - privateDnsZoneGroupName: contains(privateEndpoint, 'privateDnsZoneGroupName') ? privateEndpoint.privateDnsZoneGroupName : 'default' - privateDnsZoneResourceIds: contains(privateEndpoint, 'privateDnsZoneResourceIds') ? privateEndpoint.privateDnsZoneResourceIds : [] - 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 : [] - applicationSecurityGroupResourceIds: contains(privateEndpoint, 'applicationSecurityGroupResourceIds') ? privateEndpoint.applicationSecurityGroupResourceIds : [] - customNetworkInterfaceName: contains(privateEndpoint, 'customNetworkInterfaceName') ? privateEndpoint.customNetworkInterfaceName : '' + privateDnsZoneGroupName: privateEndpoint.?privateDnsZoneGroupName + privateDnsZoneResourceIds: privateEndpoint.?privateDnsZoneResourceIds + roleAssignments: privateEndpoint.?roleAssignments + tags: privateEndpoint.?tags ?? tags + manualPrivateLinkServiceConnections: privateEndpoint.?manualPrivateLinkServiceConnections + customDnsConfigs: privateEndpoint.?customDnsConfigs + ipConfigurations: privateEndpoint.?ipConfigurations + applicationSecurityGroupResourceIds: privateEndpoint.?applicationSecurityGroupResourceIds + customNetworkInterfaceName: privateEndpoint.?customNetworkInterfaceName } }] @@ -237,3 +240,58 @@ type roleAssignmentType = { @description('Optional. The Resource Id of the delegated managed identity resource.') delegatedManagedIdentityResourceId: string? }[]? + +type privateEndpointType = { + @description('Optional. The name of the private endpoint.') + name: string? + + @description('Optional. The location to deploy the private endpoint to.') + location: string? + + @description('Optional. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob".') + service: string? + + @description('Required. Resource ID of the subnet where the endpoint needs to be created.') + subnetResourceId: string + + @description('Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided.') + privateDnsZoneGroupName: string? + + @description('Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones.') + privateDnsZoneResourceIds: string[]? + + @description('Optional. Custom DNS configurations.') + customDnsConfigs: { + fqdn: string? + ipAddresses: string[] + }[]? + + @description('Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints.') + ipConfigurations: { + name: string + groupId: string + memberName: string + privateIpAddress: string + }[]? + + @description('Optional. Application security groups in which the private endpoint IP configuration is included.') + applicationSecurityGroupResourceIds: string[]? + + @description('Optional. The custom name of the network interface attached to the private endpoint.') + customNetworkInterfaceName: string? + + @description('Optional. Specify the type of lock.') + lock: lockType + + @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\'.') + roleAssignments: roleAssignmentType + + @description('Optional. Tags to be applied on all resources/resource groups in this deployment.') + tags: object? + + @description('Optional. Manual PrivateLink Service Connections.') + manualPrivateLinkServiceConnections: array? + + @description('Optional. Enable/Disable usage telemetry for module.') + enableTelemetry: bool? +}[]? diff --git a/modules/signal-r-service/web-pub-sub/main.json b/modules/signal-r-service/web-pub-sub/main.json index 0d635314fa..224d8e6108 100644 --- a/modules/signal-r-service/web-pub-sub/main.json +++ b/modules/signal-r-service/web-pub-sub/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.22.6.54827", - "templateHash": "7919051572076224460" + "templateHash": "17322937752748327397" }, "name": "SignalR Web PubSub Services", "description": "This module deploys a SignalR Web PubSub Service.", @@ -103,6 +103,155 @@ } }, "nullable": true + }, + "privateEndpointType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private endpoint." + } + }, + "location": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The location to deploy the private endpoint to." + } + }, + "service": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The service (sub-) type to deploy the private endpoint for. For example \"vault\" or \"blob\"." + } + }, + "subnetResourceId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the subnet where the endpoint needs to be created." + } + }, + "privateDnsZoneGroupName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided." + } + }, + "privateDnsZoneResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones." + } + }, + "customDnsConfigs": { + "type": "array", + "items": { + "type": "object", + "properties": { + "fqdn": { + "type": "string", + "nullable": true + }, + "ipAddresses": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. Custom DNS configurations." + } + }, + "ipConfigurations": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "groupId": { + "type": "string" + }, + "memberName": { + "type": "string" + }, + "privateIpAddress": { + "type": "string" + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints." + } + }, + "applicationSecurityGroupResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. Application security groups in which the private endpoint IP configuration is included." + } + }, + "customNetworkInterfaceName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The custom name of the network interface attached to the private endpoint." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "metadata": { + "description": "Optional. Specify the type of lock." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "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'." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags to be applied on all resources/resource groups in this deployment." + } + }, + "manualPrivateLinkServiceConnections": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. Manual PrivateLink Service Connections." + } + }, + "enableTelemetry": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + } + } + }, + "nullable": true } }, "parameters": { @@ -120,8 +269,7 @@ } }, "privateEndpoints": { - "type": "array", - "defaultValue": [], + "$ref": "#/definitions/privateEndpointType", "metadata": { "description": "Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible." } @@ -251,6 +399,7 @@ } ], "identityType": "[if(parameters('systemAssignedIdentity'), 'SystemAssigned', if(not(empty(parameters('userAssignedIdentities'))), 'UserAssigned', 'None'))]", + "enableReferencedModulesTelemetry": false, "identity": { "type": "[variables('identityType')]", "userAssignedIdentities": "[if(not(empty(parameters('userAssignedIdentities'))), parameters('userAssignedIdentities'), null())]" @@ -350,11 +499,11 @@ "webPubSub_privateEndpoints": { "copy": { "name": "webPubSub_privateEndpoints", - "count": "[length(parameters('privateEndpoints'))]" + "count": "[length(coalesce(parameters('privateEndpoints'), createArray()))]" }, "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", - "name": "[format('{0}-WebPubSub-PrivateEndpoint-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "name": "[format('{0}-webPubSub-PrivateEndpoint-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", "properties": { "expressionEvaluationOptions": { "scope": "inner" @@ -363,29 +512,54 @@ "parameters": { "groupIds": { "value": [ - "[parameters('privateEndpoints')[copyIndex()].service]" + "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'webpubsub')]" ] }, - "name": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'name'), createObject('value', parameters('privateEndpoints')[copyIndex()].name), createObject('value', format('pe-{0}-{1}-{2}', last(split(resourceId('Microsoft.SignalRService/webPubSub', parameters('name')), '/')), parameters('privateEndpoints')[copyIndex()].service, copyIndex())))]", + "name": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'name'), format('pep-{0}-{1}-{2}', last(split(resourceId('Microsoft.SignalRService/webPubSub', parameters('name')), '/')), coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'webpubsub'), copyIndex()))]" + }, "serviceResourceId": { "value": "[resourceId('Microsoft.SignalRService/webPubSub', parameters('name'))]" }, "subnetResourceId": { - "value": "[parameters('privateEndpoints')[copyIndex()].subnetResourceId]" + "value": "[coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId]" + }, + "enableDefaultTelemetry": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'enableDefaultTelemetry'), variables('enableReferencedModulesTelemetry'))]" + }, + "location": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'location'), reference(split(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location)]" }, - "location": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'location'), createObject('value', parameters('privateEndpoints')[copyIndex()].location), createObject('value', reference(split(parameters('privateEndpoints')[copyIndex()].subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location))]", "lock": { - "value": "[coalesce(tryGet(parameters('privateEndpoints')[copyIndex()], 'lock'), parameters('lock'))]" - }, - "privateDnsZoneGroupName": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'privateDnsZoneGroupName'), createObject('value', parameters('privateEndpoints')[copyIndex()].privateDnsZoneGroupName), createObject('value', 'default'))]", - "privateDnsZoneResourceIds": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'privateDnsZoneResourceIds'), createObject('value', parameters('privateEndpoints')[copyIndex()].privateDnsZoneResourceIds), createObject('value', createArray()))]", - "roleAssignments": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'roleAssignments'), createObject('value', parameters('privateEndpoints')[copyIndex()].roleAssignments), createObject('value', createArray()))]", - "tags": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'tags'), createObject('value', parameters('privateEndpoints')[copyIndex()].tags), createObject('value', createObject()))]", - "manualPrivateLinkServiceConnections": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'manualPrivateLinkServiceConnections'), createObject('value', parameters('privateEndpoints')[copyIndex()].manualPrivateLinkServiceConnections), createObject('value', createArray()))]", - "customDnsConfigs": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'customDnsConfigs'), createObject('value', parameters('privateEndpoints')[copyIndex()].customDnsConfigs), createObject('value', createArray()))]", - "ipConfigurations": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'ipConfigurations'), createObject('value', parameters('privateEndpoints')[copyIndex()].ipConfigurations), createObject('value', createArray()))]", - "applicationSecurityGroupResourceIds": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'applicationSecurityGroupResourceIds'), createObject('value', parameters('privateEndpoints')[copyIndex()].applicationSecurityGroupResourceIds), createObject('value', createArray()))]", - "customNetworkInterfaceName": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'customNetworkInterfaceName'), createObject('value', parameters('privateEndpoints')[copyIndex()].customNetworkInterfaceName), createObject('value', ''))]" + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'lock'), parameters('lock'))]" + }, + "privateDnsZoneGroupName": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneGroupName')]" + }, + "privateDnsZoneResourceIds": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneResourceIds')]" + }, + "roleAssignments": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'roleAssignments')]" + }, + "tags": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'tags'), parameters('tags'))]" + }, + "manualPrivateLinkServiceConnections": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'manualPrivateLinkServiceConnections')]" + }, + "customDnsConfigs": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customDnsConfigs')]" + }, + "ipConfigurations": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'ipConfigurations')]" + }, + "applicationSecurityGroupResourceIds": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'applicationSecurityGroupResourceIds')]" + }, + "customNetworkInterfaceName": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customNetworkInterfaceName')]" + } }, "template": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", diff --git a/modules/sql/server/.test/common/main.test.bicep b/modules/sql/server/.test/common/main.test.bicep index 2c57d1bbae..82ce535569 100644 --- a/modules/sql/server/.test/common/main.test.bicep +++ b/modules/sql/server/.test/common/main.test.bicep @@ -164,9 +164,7 @@ module testDeployment '../../main.bicep' = { subnetResourceId: nestedDependencies.outputs.privateEndpointSubnetResourceId service: 'sqlServer' privateDnsZoneResourceIds: [ - nestedDependencies.outputs.privateDNSZoneResourceId - ] tags: { 'hidden-title': 'This is visible in the resource name' diff --git a/modules/sql/server/.test/pe/main.test.bicep b/modules/sql/server/.test/pe/main.test.bicep index f813715f36..13c246150b 100644 --- a/modules/sql/server/.test/pe/main.test.bicep +++ b/modules/sql/server/.test/pe/main.test.bicep @@ -59,11 +59,8 @@ module testDeployment '../../main.bicep' = { privateEndpoints: [ { privateDnsZoneResourceIds: [ - nestedDependencies.outputs.privateDNSZoneResourceId - ] - service: 'sqlServer' subnetResourceId: nestedDependencies.outputs.subnetResourceId tags: { 'hidden-title': 'This is visible in the resource name' diff --git a/modules/sql/server/README.md b/modules/sql/server/README.md index 20749d71d0..0026e3da9b 100644 --- a/modules/sql/server/README.md +++ b/modules/sql/server/README.md @@ -433,7 +433,6 @@ module server 'br:bicep/modules/sql.server:1.0.0' = { privateDnsZoneResourceIds: [ '' ] - service: 'sqlServer' subnetResourceId: '' tags: { Environment: 'Non-Prod' @@ -483,7 +482,6 @@ module server 'br:bicep/modules/sql.server:1.0.0' = { "privateDnsZoneResourceIds": [ "" ], - "service": "sqlServer", "subnetResourceId": "", "tags": { "Environment": "Non-Prod", @@ -759,7 +757,168 @@ The resource ID of a user assigned identity to be used by default. Required if " Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible. - Required: No - Type: array -- Default: `[]` + + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`applicationSecurityGroupResourceIds`](#parameter-privateendpointsapplicationsecuritygroupresourceids) | No | array | Optional. Application security groups in which the private endpoint IP configuration is included. | +| [`customDnsConfigs`](#parameter-privateendpointscustomdnsconfigs) | No | array | Optional. Custom DNS configurations. | +| [`customNetworkInterfaceName`](#parameter-privateendpointscustomnetworkinterfacename) | No | string | Optional. The custom name of the network interface attached to the private endpoint. | +| [`enableTelemetry`](#parameter-privateendpointsenabletelemetry) | No | bool | Optional. Enable/Disable usage telemetry for module. | +| [`ipConfigurations`](#parameter-privateendpointsipconfigurations) | No | array | Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints. | +| [`location`](#parameter-privateendpointslocation) | No | string | Optional. The location to deploy the private endpoint to. | +| [`lock`](#parameter-privateendpointslock) | No | object | Optional. Specify the type of lock. | +| [`manualPrivateLinkServiceConnections`](#parameter-privateendpointsmanualprivatelinkserviceconnections) | No | array | Optional. Manual PrivateLink Service Connections. | +| [`name`](#parameter-privateendpointsname) | No | string | Optional. The name of the private endpoint. | +| [`privateDnsZoneGroupName`](#parameter-privateendpointsprivatednszonegroupname) | No | string | Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided. | +| [`privateDnsZoneResourceIds`](#parameter-privateendpointsprivatednszoneresourceids) | No | array | Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones. | +| [`roleAssignments`](#parameter-privateendpointsroleassignments) | No | array | 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'. | +| [`service`](#parameter-privateendpointsservice) | No | string | Optional. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob". | +| [`subnetResourceId`](#parameter-privateendpointssubnetresourceid) | Yes | string | Required. Resource ID of the subnet where the endpoint needs to be created. | +| [`tags`](#parameter-privateendpointstags) | No | object | Optional. Tags to be applied on all resources/resource groups in this deployment. | + +### Parameter: `privateEndpoints.applicationSecurityGroupResourceIds` + +Optional. Application security groups in which the private endpoint IP configuration is included. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.customDnsConfigs` + +Optional. Custom DNS configurations. + +- Required: No +- Type: array + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`fqdn`](#parameter-privateendpointscustomdnsconfigsfqdn) | No | string | | +| [`ipAddresses`](#parameter-privateendpointscustomdnsconfigsipaddresses) | Yes | array | | + +### Parameter: `privateEndpoints.customDnsConfigs.fqdn` +- Required: No +- Type: string + +### Parameter: `privateEndpoints.customDnsConfigs.ipAddresses` +- Required: Yes +- Type: array + + +### Parameter: `privateEndpoints.customNetworkInterfaceName` + +Optional. The custom name of the network interface attached to the private endpoint. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.enableTelemetry` + +Optional. Enable/Disable usage telemetry for module. + +- Required: No +- Type: bool + +### Parameter: `privateEndpoints.ipConfigurations` + +Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints. + +- Required: No +- Type: array + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`groupId`](#parameter-privateendpointsipconfigurationsgroupid) | Yes | string | | +| [`memberName`](#parameter-privateendpointsipconfigurationsmembername) | Yes | string | | +| [`name`](#parameter-privateendpointsipconfigurationsname) | Yes | string | | +| [`privateIpAddress`](#parameter-privateendpointsipconfigurationsprivateipaddress) | Yes | string | | + +### Parameter: `privateEndpoints.ipConfigurations.groupId` +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.ipConfigurations.memberName` +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.ipConfigurations.name` +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.ipConfigurations.privateIpAddress` +- Required: Yes +- Type: string + + +### Parameter: `privateEndpoints.location` + +Optional. The location to deploy the private endpoint to. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.lock` + +Optional. Specify the type of lock. + +- Required: No +- Type: object + +### Parameter: `privateEndpoints.manualPrivateLinkServiceConnections` + +Optional. Manual PrivateLink Service Connections. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.name` + +Optional. The name of the private endpoint. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.privateDnsZoneGroupName` + +Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.privateDnsZoneResourceIds` + +Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.roleAssignments` + +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'. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.service` + +Optional. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob". + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.subnetResourceId` + +Required. Resource ID of the subnet where the endpoint needs to be created. + +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.tags` + +Optional. Tags to be applied on all resources/resource groups in this deployment. + +- Required: No +- Type: object ### Parameter: `publicNetworkAccess` diff --git a/modules/sql/server/main.bicep b/modules/sql/server/main.bicep index 9a3ba48092..9b56c50287 100644 --- a/modules/sql/server/main.bicep +++ b/modules/sql/server/main.bicep @@ -66,7 +66,7 @@ param administrators object = {} param minimalTlsVersion string = '1.2' @description('Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible.') -param privateEndpoints array = [] +param privateEndpoints privateEndpointType @description('Optional. Whether or not public network access is allowed for this resource. For security reasons it should be disabled. If not specified, it will be disabled by default if private endpoints are set and neither firewall rules nor virtual network rules are set.') @allowed([ @@ -241,27 +241,27 @@ module server_elasticPools 'elastic-pool/main.bicep' = [for (elasticPool, index) } }] -module server_privateEndpoints '../../network/private-endpoint/main.bicep' = [for (privateEndpoint, index) in privateEndpoints: { - name: '${uniqueString(deployment().name, location)}-SQLServer-PrivateEndpoint-${index}' +module server_privateEndpoints '../../network/private-endpoint/main.bicep' = [for (privateEndpoint, index) in (privateEndpoints ?? []): { + name: '${uniqueString(deployment().name, location)}-server-PrivateEndpoint-${index}' params: { groupIds: [ - privateEndpoint.service + privateEndpoint.?service ?? 'sqlServer' ] - name: contains(privateEndpoint, 'name') ? privateEndpoint.name : 'pe-${last(split(server.id, '/'))}-${privateEndpoint.service}-${index}' + name: privateEndpoint.?name ?? 'pep-${last(split(server.id, '/'))}-${privateEndpoint.?service ?? 'sqlServer'}-${index}' serviceResourceId: server.id subnetResourceId: privateEndpoint.subnetResourceId - enableDefaultTelemetry: enableReferencedModulesTelemetry - location: contains(privateEndpoint, 'location') ? privateEndpoint.location : reference(split(privateEndpoint.subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location + enableDefaultTelemetry: privateEndpoint.?enableDefaultTelemetry ?? enableReferencedModulesTelemetry + location: privateEndpoint.?location ?? reference(split(privateEndpoint.subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location lock: privateEndpoint.?lock ?? lock - privateDnsZoneGroupName: contains(privateEndpoint, 'privateDnsZoneGroupName') ? privateEndpoint.privateDnsZoneGroupName : 'default' - privateDnsZoneResourceIds: contains(privateEndpoint, 'privateDnsZoneResourceIds') ? privateEndpoint.privateDnsZoneResourceIds : [] - 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 : [] - applicationSecurityGroupResourceIds: contains(privateEndpoint, 'applicationSecurityGroupResourceIds') ? privateEndpoint.applicationSecurityGroupResourceIds : [] - customNetworkInterfaceName: contains(privateEndpoint, 'customNetworkInterfaceName') ? privateEndpoint.customNetworkInterfaceName : '' + privateDnsZoneGroupName: privateEndpoint.?privateDnsZoneGroupName + privateDnsZoneResourceIds: privateEndpoint.?privateDnsZoneResourceIds + roleAssignments: privateEndpoint.?roleAssignments + tags: privateEndpoint.?tags ?? tags + manualPrivateLinkServiceConnections: privateEndpoint.?manualPrivateLinkServiceConnections + customDnsConfigs: privateEndpoint.?customDnsConfigs + ipConfigurations: privateEndpoint.?ipConfigurations + applicationSecurityGroupResourceIds: privateEndpoint.?applicationSecurityGroupResourceIds + customNetworkInterfaceName: privateEndpoint.?customNetworkInterfaceName } }] @@ -393,3 +393,58 @@ type roleAssignmentType = { @description('Optional. The Resource Id of the delegated managed identity resource.') delegatedManagedIdentityResourceId: string? }[]? + +type privateEndpointType = { + @description('Optional. The name of the private endpoint.') + name: string? + + @description('Optional. The location to deploy the private endpoint to.') + location: string? + + @description('Optional. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob".') + service: string? + + @description('Required. Resource ID of the subnet where the endpoint needs to be created.') + subnetResourceId: string + + @description('Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided.') + privateDnsZoneGroupName: string? + + @description('Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones.') + privateDnsZoneResourceIds: string[]? + + @description('Optional. Custom DNS configurations.') + customDnsConfigs: { + fqdn: string? + ipAddresses: string[] + }[]? + + @description('Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints.') + ipConfigurations: { + name: string + groupId: string + memberName: string + privateIpAddress: string + }[]? + + @description('Optional. Application security groups in which the private endpoint IP configuration is included.') + applicationSecurityGroupResourceIds: string[]? + + @description('Optional. The custom name of the network interface attached to the private endpoint.') + customNetworkInterfaceName: string? + + @description('Optional. Specify the type of lock.') + lock: lockType + + @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\'.') + roleAssignments: roleAssignmentType + + @description('Optional. Tags to be applied on all resources/resource groups in this deployment.') + tags: object? + + @description('Optional. Manual PrivateLink Service Connections.') + manualPrivateLinkServiceConnections: array? + + @description('Optional. Enable/Disable usage telemetry for module.') + enableTelemetry: bool? +}[]? diff --git a/modules/sql/server/main.json b/modules/sql/server/main.json index 8becec3ebd..6323877d94 100644 --- a/modules/sql/server/main.json +++ b/modules/sql/server/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.22.6.54827", - "templateHash": "6602628409746140291" + "templateHash": "15785900556035209583" }, "name": "Azure SQL Servers", "description": "This module deploys an Azure SQL Server.", @@ -103,6 +103,155 @@ } }, "nullable": true + }, + "privateEndpointType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private endpoint." + } + }, + "location": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The location to deploy the private endpoint to." + } + }, + "service": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The service (sub-) type to deploy the private endpoint for. For example \"vault\" or \"blob\"." + } + }, + "subnetResourceId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the subnet where the endpoint needs to be created." + } + }, + "privateDnsZoneGroupName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided." + } + }, + "privateDnsZoneResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones." + } + }, + "customDnsConfigs": { + "type": "array", + "items": { + "type": "object", + "properties": { + "fqdn": { + "type": "string", + "nullable": true + }, + "ipAddresses": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. Custom DNS configurations." + } + }, + "ipConfigurations": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "groupId": { + "type": "string" + }, + "memberName": { + "type": "string" + }, + "privateIpAddress": { + "type": "string" + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints." + } + }, + "applicationSecurityGroupResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. Application security groups in which the private endpoint IP configuration is included." + } + }, + "customNetworkInterfaceName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The custom name of the network interface attached to the private endpoint." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "metadata": { + "description": "Optional. Specify the type of lock." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "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'." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags to be applied on all resources/resource groups in this deployment." + } + }, + "manualPrivateLinkServiceConnections": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. Manual PrivateLink Service Connections." + } + }, + "enableTelemetry": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + } + } + }, + "nullable": true } }, "parameters": { @@ -242,8 +391,7 @@ } }, "privateEndpoints": { - "type": "array", - "defaultValue": [], + "$ref": "#/definitions/privateEndpointType", "metadata": { "description": "Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible." } @@ -1395,11 +1543,11 @@ "server_privateEndpoints": { "copy": { "name": "server_privateEndpoints", - "count": "[length(parameters('privateEndpoints'))]" + "count": "[length(coalesce(parameters('privateEndpoints'), createArray()))]" }, "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", - "name": "[format('{0}-SQLServer-PrivateEndpoint-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "name": "[format('{0}-server-PrivateEndpoint-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", "properties": { "expressionEvaluationOptions": { "scope": "inner" @@ -1408,32 +1556,54 @@ "parameters": { "groupIds": { "value": [ - "[parameters('privateEndpoints')[copyIndex()].service]" + "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'sqlServer')]" ] }, - "name": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'name'), createObject('value', parameters('privateEndpoints')[copyIndex()].name), createObject('value', format('pe-{0}-{1}-{2}', last(split(resourceId('Microsoft.Sql/servers', parameters('name')), '/')), parameters('privateEndpoints')[copyIndex()].service, copyIndex())))]", + "name": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'name'), format('pep-{0}-{1}-{2}', last(split(resourceId('Microsoft.Sql/servers', parameters('name')), '/')), coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'sqlServer'), copyIndex()))]" + }, "serviceResourceId": { "value": "[resourceId('Microsoft.Sql/servers', parameters('name'))]" }, "subnetResourceId": { - "value": "[parameters('privateEndpoints')[copyIndex()].subnetResourceId]" + "value": "[coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId]" }, "enableDefaultTelemetry": { - "value": "[variables('enableReferencedModulesTelemetry')]" + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'enableDefaultTelemetry'), variables('enableReferencedModulesTelemetry'))]" + }, + "location": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'location'), reference(split(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location)]" }, - "location": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'location'), createObject('value', parameters('privateEndpoints')[copyIndex()].location), createObject('value', reference(split(parameters('privateEndpoints')[copyIndex()].subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location))]", "lock": { - "value": "[coalesce(tryGet(parameters('privateEndpoints')[copyIndex()], 'lock'), parameters('lock'))]" - }, - "privateDnsZoneGroupName": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'privateDnsZoneGroupName'), createObject('value', parameters('privateEndpoints')[copyIndex()].privateDnsZoneGroupName), createObject('value', 'default'))]", - "privateDnsZoneResourceIds": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'privateDnsZoneResourceIds'), createObject('value', parameters('privateEndpoints')[copyIndex()].privateDnsZoneResourceIds), createObject('value', createArray()))]", - "roleAssignments": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'roleAssignments'), createObject('value', parameters('privateEndpoints')[copyIndex()].roleAssignments), createObject('value', createArray()))]", - "tags": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'tags'), createObject('value', parameters('privateEndpoints')[copyIndex()].tags), createObject('value', createObject()))]", - "manualPrivateLinkServiceConnections": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'manualPrivateLinkServiceConnections'), createObject('value', parameters('privateEndpoints')[copyIndex()].manualPrivateLinkServiceConnections), createObject('value', createArray()))]", - "customDnsConfigs": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'customDnsConfigs'), createObject('value', parameters('privateEndpoints')[copyIndex()].customDnsConfigs), createObject('value', createArray()))]", - "ipConfigurations": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'ipConfigurations'), createObject('value', parameters('privateEndpoints')[copyIndex()].ipConfigurations), createObject('value', createArray()))]", - "applicationSecurityGroupResourceIds": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'applicationSecurityGroupResourceIds'), createObject('value', parameters('privateEndpoints')[copyIndex()].applicationSecurityGroupResourceIds), createObject('value', createArray()))]", - "customNetworkInterfaceName": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'customNetworkInterfaceName'), createObject('value', parameters('privateEndpoints')[copyIndex()].customNetworkInterfaceName), createObject('value', ''))]" + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'lock'), parameters('lock'))]" + }, + "privateDnsZoneGroupName": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneGroupName')]" + }, + "privateDnsZoneResourceIds": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneResourceIds')]" + }, + "roleAssignments": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'roleAssignments')]" + }, + "tags": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'tags'), parameters('tags'))]" + }, + "manualPrivateLinkServiceConnections": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'manualPrivateLinkServiceConnections')]" + }, + "customDnsConfigs": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customDnsConfigs')]" + }, + "ipConfigurations": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'ipConfigurations')]" + }, + "applicationSecurityGroupResourceIds": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'applicationSecurityGroupResourceIds')]" + }, + "customNetworkInterfaceName": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customNetworkInterfaceName')]" + } }, "template": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", diff --git a/modules/storage/storage-account/README.md b/modules/storage/storage-account/README.md index 5beadf8bb9..1193a3c7fd 100644 --- a/modules/storage/storage-account/README.md +++ b/modules/storage/storage-account/README.md @@ -1343,7 +1343,168 @@ Networks ACLs, this value contains IPs to whitelist and/or Subnet information. F Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible. - Required: No - Type: array -- Default: `[]` + + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`applicationSecurityGroupResourceIds`](#parameter-privateendpointsapplicationsecuritygroupresourceids) | No | array | Optional. Application security groups in which the private endpoint IP configuration is included. | +| [`customDnsConfigs`](#parameter-privateendpointscustomdnsconfigs) | No | array | Optional. Custom DNS configurations. | +| [`customNetworkInterfaceName`](#parameter-privateendpointscustomnetworkinterfacename) | No | string | Optional. The custom name of the network interface attached to the private endpoint. | +| [`enableTelemetry`](#parameter-privateendpointsenabletelemetry) | No | bool | Optional. Enable/Disable usage telemetry for module. | +| [`ipConfigurations`](#parameter-privateendpointsipconfigurations) | No | array | Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints. | +| [`location`](#parameter-privateendpointslocation) | No | string | Optional. The location to deploy the private endpoint to. | +| [`lock`](#parameter-privateendpointslock) | No | object | Optional. Specify the type of lock. | +| [`manualPrivateLinkServiceConnections`](#parameter-privateendpointsmanualprivatelinkserviceconnections) | No | array | Optional. Manual PrivateLink Service Connections. | +| [`name`](#parameter-privateendpointsname) | No | string | Optional. The name of the private endpoint. | +| [`privateDnsZoneGroupName`](#parameter-privateendpointsprivatednszonegroupname) | No | string | Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided. | +| [`privateDnsZoneResourceIds`](#parameter-privateendpointsprivatednszoneresourceids) | No | array | Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones. | +| [`roleAssignments`](#parameter-privateendpointsroleassignments) | No | array | 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'. | +| [`service`](#parameter-privateendpointsservice) | Yes | string | Required. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob". | +| [`subnetResourceId`](#parameter-privateendpointssubnetresourceid) | Yes | string | Required. Resource ID of the subnet where the endpoint needs to be created. | +| [`tags`](#parameter-privateendpointstags) | No | object | Optional. Tags to be applied on all resources/resource groups in this deployment. | + +### Parameter: `privateEndpoints.applicationSecurityGroupResourceIds` + +Optional. Application security groups in which the private endpoint IP configuration is included. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.customDnsConfigs` + +Optional. Custom DNS configurations. + +- Required: No +- Type: array + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`fqdn`](#parameter-privateendpointscustomdnsconfigsfqdn) | No | string | | +| [`ipAddresses`](#parameter-privateendpointscustomdnsconfigsipaddresses) | Yes | array | | + +### Parameter: `privateEndpoints.customDnsConfigs.fqdn` +- Required: No +- Type: string + +### Parameter: `privateEndpoints.customDnsConfigs.ipAddresses` +- Required: Yes +- Type: array + + +### Parameter: `privateEndpoints.customNetworkInterfaceName` + +Optional. The custom name of the network interface attached to the private endpoint. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.enableTelemetry` + +Optional. Enable/Disable usage telemetry for module. + +- Required: No +- Type: bool + +### Parameter: `privateEndpoints.ipConfigurations` + +Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints. + +- Required: No +- Type: array + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`groupId`](#parameter-privateendpointsipconfigurationsgroupid) | Yes | string | | +| [`memberName`](#parameter-privateendpointsipconfigurationsmembername) | Yes | string | | +| [`name`](#parameter-privateendpointsipconfigurationsname) | Yes | string | | +| [`privateIpAddress`](#parameter-privateendpointsipconfigurationsprivateipaddress) | Yes | string | | + +### Parameter: `privateEndpoints.ipConfigurations.groupId` +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.ipConfigurations.memberName` +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.ipConfigurations.name` +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.ipConfigurations.privateIpAddress` +- Required: Yes +- Type: string + + +### Parameter: `privateEndpoints.location` + +Optional. The location to deploy the private endpoint to. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.lock` + +Optional. Specify the type of lock. + +- Required: No +- Type: object + +### Parameter: `privateEndpoints.manualPrivateLinkServiceConnections` + +Optional. Manual PrivateLink Service Connections. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.name` + +Optional. The name of the private endpoint. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.privateDnsZoneGroupName` + +Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.privateDnsZoneResourceIds` + +Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.roleAssignments` + +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'. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.service` + +Required. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob". + +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.subnetResourceId` + +Required. Resource ID of the subnet where the endpoint needs to be created. + +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.tags` + +Optional. Tags to be applied on all resources/resource groups in this deployment. + +- Required: No +- Type: object ### Parameter: `publicNetworkAccess` diff --git a/modules/storage/storage-account/main.bicep b/modules/storage/storage-account/main.bicep index dafb2589f2..e8b9925a88 100644 --- a/modules/storage/storage-account/main.bicep +++ b/modules/storage/storage-account/main.bicep @@ -66,7 +66,7 @@ param defaultToOAuthAuthentication bool = false param allowSharedKeyAccess bool = true @description('Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible.') -param privateEndpoints array = [] +param privateEndpoints privateEndpointType @description('Optional. The Storage Account ManagementPolicies Rules.') param managementPolicyRules array = [] @@ -361,27 +361,27 @@ resource storageAccount_roleAssignments 'Microsoft.Authorization/roleAssignments scope: storageAccount }] -module storageAccount_privateEndpoints '../../network/private-endpoint/main.bicep' = [for (privateEndpoint, index) in privateEndpoints: { - name: '${uniqueString(deployment().name, location)}-StorageAccount-PrivateEndpoint-${index}' +module storageAccount_privateEndpoints '../../network/private-endpoint/main.bicep' = [for (privateEndpoint, index) in (privateEndpoints ?? []): { + name: '${uniqueString(deployment().name, location)}-storageAccount-PrivateEndpoint-${index}' params: { groupIds: [ privateEndpoint.service ] - name: contains(privateEndpoint, 'name') ? privateEndpoint.name : 'pe-${last(split(storageAccount.id, '/'))}-${privateEndpoint.service}-${index}' + name: privateEndpoint.?name ?? 'pep-${last(split(storageAccount.id, '/'))}-${privateEndpoint.?service ?? privateEndpoint.service}-${index}' serviceResourceId: storageAccount.id subnetResourceId: privateEndpoint.subnetResourceId - enableDefaultTelemetry: enableReferencedModulesTelemetry - location: contains(privateEndpoint, 'location') ? privateEndpoint.location : reference(split(privateEndpoint.subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location + enableDefaultTelemetry: privateEndpoint.?enableDefaultTelemetry ?? enableReferencedModulesTelemetry + location: privateEndpoint.?location ?? reference(split(privateEndpoint.subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location lock: privateEndpoint.?lock ?? lock - privateDnsZoneGroupName: contains(privateEndpoint, 'privateDnsZoneGroupName') ? privateEndpoint.privateDnsZoneGroupName : 'default' - privateDnsZoneResourceIds: contains(privateEndpoint, 'privateDnsZoneResourceIds') ? privateEndpoint.privateDnsZoneResourceIds : [] - 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 : [] - applicationSecurityGroupResourceIds: contains(privateEndpoint, 'applicationSecurityGroupResourceIds') ? privateEndpoint.applicationSecurityGroupResourceIds : [] - customNetworkInterfaceName: contains(privateEndpoint, 'customNetworkInterfaceName') ? privateEndpoint.customNetworkInterfaceName : '' + privateDnsZoneGroupName: privateEndpoint.?privateDnsZoneGroupName + privateDnsZoneResourceIds: privateEndpoint.?privateDnsZoneResourceIds + roleAssignments: privateEndpoint.?roleAssignments + tags: privateEndpoint.?tags ?? tags + manualPrivateLinkServiceConnections: privateEndpoint.?manualPrivateLinkServiceConnections + customDnsConfigs: privateEndpoint.?customDnsConfigs + ipConfigurations: privateEndpoint.?ipConfigurations + applicationSecurityGroupResourceIds: privateEndpoint.?applicationSecurityGroupResourceIds + customNetworkInterfaceName: privateEndpoint.?customNetworkInterfaceName } }] @@ -550,3 +550,58 @@ type roleAssignmentType = { @description('Optional. The Resource Id of the delegated managed identity resource.') delegatedManagedIdentityResourceId: string? }[]? + +type privateEndpointType = { + @description('Optional. The name of the private endpoint.') + name: string? + + @description('Optional. The location to deploy the private endpoint to.') + location: string? + + @description('Required. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob".') + service: string + + @description('Required. Resource ID of the subnet where the endpoint needs to be created.') + subnetResourceId: string + + @description('Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided.') + privateDnsZoneGroupName: string? + + @description('Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones.') + privateDnsZoneResourceIds: string[]? + + @description('Optional. Custom DNS configurations.') + customDnsConfigs: { + fqdn: string? + ipAddresses: string[] + }[]? + + @description('Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints.') + ipConfigurations: { + name: string + groupId: string + memberName: string + privateIpAddress: string + }[]? + + @description('Optional. Application security groups in which the private endpoint IP configuration is included.') + applicationSecurityGroupResourceIds: string[]? + + @description('Optional. The custom name of the network interface attached to the private endpoint.') + customNetworkInterfaceName: string? + + @description('Optional. Specify the type of lock.') + lock: lockType + + @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\'.') + roleAssignments: roleAssignmentType + + @description('Optional. Tags to be applied on all resources/resource groups in this deployment.') + tags: object? + + @description('Optional. Manual PrivateLink Service Connections.') + manualPrivateLinkServiceConnections: array? + + @description('Optional. Enable/Disable usage telemetry for module.') + enableTelemetry: bool? +}[]? diff --git a/modules/storage/storage-account/main.json b/modules/storage/storage-account/main.json index fdebcc9bdf..ae9fba4c9b 100644 --- a/modules/storage/storage-account/main.json +++ b/modules/storage/storage-account/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.22.6.54827", - "templateHash": "12807881616729507615" + "templateHash": "7816141440918547974" }, "name": "Storage Accounts", "description": "This module deploys a Storage Account.", @@ -103,6 +103,154 @@ } }, "nullable": true + }, + "privateEndpointType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private endpoint." + } + }, + "location": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The location to deploy the private endpoint to." + } + }, + "service": { + "type": "string", + "metadata": { + "description": "Required. The service (sub-) type to deploy the private endpoint for. For example \"vault\" or \"blob\"." + } + }, + "subnetResourceId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the subnet where the endpoint needs to be created." + } + }, + "privateDnsZoneGroupName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided." + } + }, + "privateDnsZoneResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones." + } + }, + "customDnsConfigs": { + "type": "array", + "items": { + "type": "object", + "properties": { + "fqdn": { + "type": "string", + "nullable": true + }, + "ipAddresses": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. Custom DNS configurations." + } + }, + "ipConfigurations": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "groupId": { + "type": "string" + }, + "memberName": { + "type": "string" + }, + "privateIpAddress": { + "type": "string" + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints." + } + }, + "applicationSecurityGroupResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. Application security groups in which the private endpoint IP configuration is included." + } + }, + "customNetworkInterfaceName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The custom name of the network interface attached to the private endpoint." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "metadata": { + "description": "Optional. Specify the type of lock." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "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'." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags to be applied on all resources/resource groups in this deployment." + } + }, + "manualPrivateLinkServiceConnections": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. Manual PrivateLink Service Connections." + } + }, + "enableTelemetry": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + } + } + }, + "nullable": true } }, "parameters": { @@ -216,8 +364,7 @@ } }, "privateEndpoints": { - "type": "array", - "defaultValue": [], + "$ref": "#/definitions/privateEndpointType", "metadata": { "description": "Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible." } @@ -669,11 +816,11 @@ "storageAccount_privateEndpoints": { "copy": { "name": "storageAccount_privateEndpoints", - "count": "[length(parameters('privateEndpoints'))]" + "count": "[length(coalesce(parameters('privateEndpoints'), createArray()))]" }, "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", - "name": "[format('{0}-StorageAccount-PrivateEndpoint-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "name": "[format('{0}-storageAccount-PrivateEndpoint-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", "properties": { "expressionEvaluationOptions": { "scope": "inner" @@ -682,32 +829,54 @@ "parameters": { "groupIds": { "value": [ - "[parameters('privateEndpoints')[copyIndex()].service]" + "[coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].service]" ] }, - "name": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'name'), createObject('value', parameters('privateEndpoints')[copyIndex()].name), createObject('value', format('pe-{0}-{1}-{2}', last(split(resourceId('Microsoft.Storage/storageAccounts', parameters('name')), '/')), parameters('privateEndpoints')[copyIndex()].service, copyIndex())))]", + "name": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'name'), format('pep-{0}-{1}-{2}', last(split(resourceId('Microsoft.Storage/storageAccounts', parameters('name')), '/')), coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].service), copyIndex()))]" + }, "serviceResourceId": { "value": "[resourceId('Microsoft.Storage/storageAccounts', parameters('name'))]" }, "subnetResourceId": { - "value": "[parameters('privateEndpoints')[copyIndex()].subnetResourceId]" + "value": "[coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId]" }, "enableDefaultTelemetry": { - "value": "[variables('enableReferencedModulesTelemetry')]" + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'enableDefaultTelemetry'), variables('enableReferencedModulesTelemetry'))]" + }, + "location": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'location'), reference(split(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location)]" }, - "location": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'location'), createObject('value', parameters('privateEndpoints')[copyIndex()].location), createObject('value', reference(split(parameters('privateEndpoints')[copyIndex()].subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location))]", "lock": { - "value": "[coalesce(tryGet(parameters('privateEndpoints')[copyIndex()], 'lock'), parameters('lock'))]" - }, - "privateDnsZoneGroupName": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'privateDnsZoneGroupName'), createObject('value', parameters('privateEndpoints')[copyIndex()].privateDnsZoneGroupName), createObject('value', 'default'))]", - "privateDnsZoneResourceIds": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'privateDnsZoneResourceIds'), createObject('value', parameters('privateEndpoints')[copyIndex()].privateDnsZoneResourceIds), createObject('value', createArray()))]", - "roleAssignments": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'roleAssignments'), createObject('value', parameters('privateEndpoints')[copyIndex()].roleAssignments), createObject('value', createArray()))]", - "tags": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'tags'), createObject('value', parameters('privateEndpoints')[copyIndex()].tags), createObject('value', createObject()))]", - "manualPrivateLinkServiceConnections": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'manualPrivateLinkServiceConnections'), createObject('value', parameters('privateEndpoints')[copyIndex()].manualPrivateLinkServiceConnections), createObject('value', createArray()))]", - "customDnsConfigs": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'customDnsConfigs'), createObject('value', parameters('privateEndpoints')[copyIndex()].customDnsConfigs), createObject('value', createArray()))]", - "ipConfigurations": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'ipConfigurations'), createObject('value', parameters('privateEndpoints')[copyIndex()].ipConfigurations), createObject('value', createArray()))]", - "applicationSecurityGroupResourceIds": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'applicationSecurityGroupResourceIds'), createObject('value', parameters('privateEndpoints')[copyIndex()].applicationSecurityGroupResourceIds), createObject('value', createArray()))]", - "customNetworkInterfaceName": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'customNetworkInterfaceName'), createObject('value', parameters('privateEndpoints')[copyIndex()].customNetworkInterfaceName), createObject('value', ''))]" + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'lock'), parameters('lock'))]" + }, + "privateDnsZoneGroupName": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneGroupName')]" + }, + "privateDnsZoneResourceIds": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneResourceIds')]" + }, + "roleAssignments": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'roleAssignments')]" + }, + "tags": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'tags'), parameters('tags'))]" + }, + "manualPrivateLinkServiceConnections": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'manualPrivateLinkServiceConnections')]" + }, + "customDnsConfigs": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customDnsConfigs')]" + }, + "ipConfigurations": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'ipConfigurations')]" + }, + "applicationSecurityGroupResourceIds": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'applicationSecurityGroupResourceIds')]" + }, + "customNetworkInterfaceName": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customNetworkInterfaceName')]" + } }, "template": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", diff --git a/modules/synapse/private-link-hub/.test/common/main.test.bicep b/modules/synapse/private-link-hub/.test/common/main.test.bicep index 7b59ec1474..7b553d1f14 100644 --- a/modules/synapse/private-link-hub/.test/common/main.test.bicep +++ b/modules/synapse/private-link-hub/.test/common/main.test.bicep @@ -61,9 +61,7 @@ module testDeployment '../../main.bicep' = { privateEndpoints: [ { privateDnsZoneResourceIds: [ - nestedDependencies.outputs.privateDNSZoneResourceId - ] service: 'Web' subnetResourceId: nestedDependencies.outputs.subnetResourceId diff --git a/modules/synapse/private-link-hub/README.md b/modules/synapse/private-link-hub/README.md index d9a6f2056f..2a0b0cff04 100644 --- a/modules/synapse/private-link-hub/README.md +++ b/modules/synapse/private-link-hub/README.md @@ -277,7 +277,168 @@ The name of the Private Link Hub. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible. - Required: No - Type: array -- Default: `[]` + + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`applicationSecurityGroupResourceIds`](#parameter-privateendpointsapplicationsecuritygroupresourceids) | No | array | Optional. Application security groups in which the private endpoint IP configuration is included. | +| [`customDnsConfigs`](#parameter-privateendpointscustomdnsconfigs) | No | array | Optional. Custom DNS configurations. | +| [`customNetworkInterfaceName`](#parameter-privateendpointscustomnetworkinterfacename) | No | string | Optional. The custom name of the network interface attached to the private endpoint. | +| [`enableTelemetry`](#parameter-privateendpointsenabletelemetry) | No | bool | Optional. Enable/Disable usage telemetry for module. | +| [`ipConfigurations`](#parameter-privateendpointsipconfigurations) | No | array | Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints. | +| [`location`](#parameter-privateendpointslocation) | No | string | Optional. The location to deploy the private endpoint to. | +| [`lock`](#parameter-privateendpointslock) | No | object | Optional. Specify the type of lock. | +| [`manualPrivateLinkServiceConnections`](#parameter-privateendpointsmanualprivatelinkserviceconnections) | No | array | Optional. Manual PrivateLink Service Connections. | +| [`name`](#parameter-privateendpointsname) | No | string | Optional. The name of the private endpoint. | +| [`privateDnsZoneGroupName`](#parameter-privateendpointsprivatednszonegroupname) | No | string | Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided. | +| [`privateDnsZoneResourceIds`](#parameter-privateendpointsprivatednszoneresourceids) | No | array | Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones. | +| [`roleAssignments`](#parameter-privateendpointsroleassignments) | No | array | 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'. | +| [`service`](#parameter-privateendpointsservice) | Yes | string | Required. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob". | +| [`subnetResourceId`](#parameter-privateendpointssubnetresourceid) | Yes | string | Required. Resource ID of the subnet where the endpoint needs to be created. | +| [`tags`](#parameter-privateendpointstags) | No | object | Optional. Tags to be applied on all resources/resource groups in this deployment. | + +### Parameter: `privateEndpoints.applicationSecurityGroupResourceIds` + +Optional. Application security groups in which the private endpoint IP configuration is included. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.customDnsConfigs` + +Optional. Custom DNS configurations. + +- Required: No +- Type: array + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`fqdn`](#parameter-privateendpointscustomdnsconfigsfqdn) | No | string | | +| [`ipAddresses`](#parameter-privateendpointscustomdnsconfigsipaddresses) | Yes | array | | + +### Parameter: `privateEndpoints.customDnsConfigs.fqdn` +- Required: No +- Type: string + +### Parameter: `privateEndpoints.customDnsConfigs.ipAddresses` +- Required: Yes +- Type: array + + +### Parameter: `privateEndpoints.customNetworkInterfaceName` + +Optional. The custom name of the network interface attached to the private endpoint. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.enableTelemetry` + +Optional. Enable/Disable usage telemetry for module. + +- Required: No +- Type: bool + +### Parameter: `privateEndpoints.ipConfigurations` + +Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints. + +- Required: No +- Type: array + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`groupId`](#parameter-privateendpointsipconfigurationsgroupid) | Yes | string | | +| [`memberName`](#parameter-privateendpointsipconfigurationsmembername) | Yes | string | | +| [`name`](#parameter-privateendpointsipconfigurationsname) | Yes | string | | +| [`privateIpAddress`](#parameter-privateendpointsipconfigurationsprivateipaddress) | Yes | string | | + +### Parameter: `privateEndpoints.ipConfigurations.groupId` +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.ipConfigurations.memberName` +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.ipConfigurations.name` +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.ipConfigurations.privateIpAddress` +- Required: Yes +- Type: string + + +### Parameter: `privateEndpoints.location` + +Optional. The location to deploy the private endpoint to. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.lock` + +Optional. Specify the type of lock. + +- Required: No +- Type: object + +### Parameter: `privateEndpoints.manualPrivateLinkServiceConnections` + +Optional. Manual PrivateLink Service Connections. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.name` + +Optional. The name of the private endpoint. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.privateDnsZoneGroupName` + +Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.privateDnsZoneResourceIds` + +Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.roleAssignments` + +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'. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.service` + +Required. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob". + +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.subnetResourceId` + +Required. Resource ID of the subnet where the endpoint needs to be created. + +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.tags` + +Optional. Tags to be applied on all resources/resource groups in this deployment. + +- Required: No +- Type: object ### Parameter: `roleAssignments` diff --git a/modules/synapse/private-link-hub/main.bicep b/modules/synapse/private-link-hub/main.bicep index a0a809ed93..003f53a1dd 100644 --- a/modules/synapse/private-link-hub/main.bicep +++ b/modules/synapse/private-link-hub/main.bicep @@ -21,7 +21,7 @@ param enableDefaultTelemetry bool = true param roleAssignments roleAssignmentType @description('Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible.') -param privateEndpoints array = [] +param privateEndpoints privateEndpointType var enableReferencedModulesTelemetry = false @@ -77,27 +77,27 @@ resource privateLinkHub_roleAssignments 'Microsoft.Authorization/roleAssignments }] // Private Endpoints -module privateLinkHub_privateEndpoints '../../network/private-endpoint/main.bicep' = [for (privateEndpoint, index) in privateEndpoints: { - name: '${uniqueString(deployment().name, location)}-PrivateLinkHub-PrivateEndpoint-${index}' +module privateLinkHub_privateEndpoints '../../network/private-endpoint/main.bicep' = [for (privateEndpoint, index) in (privateEndpoints ?? []): { + name: '${uniqueString(deployment().name, location)}-privateLinkHub-PrivateEndpoint-${index}' params: { groupIds: [ privateEndpoint.service ] - name: contains(privateEndpoint, 'name') ? privateEndpoint.name : 'pe-${last(split(privateLinkHub.id, '/'))}-${privateEndpoint.service}-${index}' + name: privateEndpoint.?name ?? 'pep-${last(split(privateLinkHub.id, '/'))}-${privateEndpoint.?service ?? privateEndpoint.service}-${index}' serviceResourceId: privateLinkHub.id subnetResourceId: privateEndpoint.subnetResourceId - enableDefaultTelemetry: enableReferencedModulesTelemetry - location: contains(privateEndpoint, 'location') ? privateEndpoint.location : reference(split(privateEndpoint.subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location + enableDefaultTelemetry: privateEndpoint.?enableDefaultTelemetry ?? enableReferencedModulesTelemetry + location: privateEndpoint.?location ?? reference(split(privateEndpoint.subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location lock: privateEndpoint.?lock ?? lock - privateDnsZoneGroupName: contains(privateEndpoint, 'privateDnsZoneGroupName') ? privateEndpoint.privateDnsZoneGroupName : 'default' - privateDnsZoneResourceIds: contains(privateEndpoint, 'privateDnsZoneResourceIds') ? privateEndpoint.privateDnsZoneResourceIds : [] - 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 : [] - applicationSecurityGroupResourceIds: contains(privateEndpoint, 'applicationSecurityGroupResourceIds') ? privateEndpoint.applicationSecurityGroupResourceIds : [] - customNetworkInterfaceName: contains(privateEndpoint, 'customNetworkInterfaceName') ? privateEndpoint.customNetworkInterfaceName : '' + privateDnsZoneGroupName: privateEndpoint.?privateDnsZoneGroupName + privateDnsZoneResourceIds: privateEndpoint.?privateDnsZoneResourceIds + roleAssignments: privateEndpoint.?roleAssignments + tags: privateEndpoint.?tags ?? tags + manualPrivateLinkServiceConnections: privateEndpoint.?manualPrivateLinkServiceConnections + customDnsConfigs: privateEndpoint.?customDnsConfigs + ipConfigurations: privateEndpoint.?ipConfigurations + applicationSecurityGroupResourceIds: privateEndpoint.?applicationSecurityGroupResourceIds + customNetworkInterfaceName: privateEndpoint.?customNetworkInterfaceName } }] @@ -147,3 +147,58 @@ type roleAssignmentType = { @description('Optional. The Resource Id of the delegated managed identity resource.') delegatedManagedIdentityResourceId: string? }[]? + +type privateEndpointType = { + @description('Optional. The name of the private endpoint.') + name: string? + + @description('Optional. The location to deploy the private endpoint to.') + location: string? + + @description('Required. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob".') + service: string + + @description('Required. Resource ID of the subnet where the endpoint needs to be created.') + subnetResourceId: string + + @description('Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided.') + privateDnsZoneGroupName: string? + + @description('Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones.') + privateDnsZoneResourceIds: string[]? + + @description('Optional. Custom DNS configurations.') + customDnsConfigs: { + fqdn: string? + ipAddresses: string[] + }[]? + + @description('Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints.') + ipConfigurations: { + name: string + groupId: string + memberName: string + privateIpAddress: string + }[]? + + @description('Optional. Application security groups in which the private endpoint IP configuration is included.') + applicationSecurityGroupResourceIds: string[]? + + @description('Optional. The custom name of the network interface attached to the private endpoint.') + customNetworkInterfaceName: string? + + @description('Optional. Specify the type of lock.') + lock: lockType + + @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\'.') + roleAssignments: roleAssignmentType + + @description('Optional. Tags to be applied on all resources/resource groups in this deployment.') + tags: object? + + @description('Optional. Manual PrivateLink Service Connections.') + manualPrivateLinkServiceConnections: array? + + @description('Optional. Enable/Disable usage telemetry for module.') + enableTelemetry: bool? +}[]? diff --git a/modules/synapse/private-link-hub/main.json b/modules/synapse/private-link-hub/main.json index 903af8a9c2..4fab3419d1 100644 --- a/modules/synapse/private-link-hub/main.json +++ b/modules/synapse/private-link-hub/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.22.6.54827", - "templateHash": "17913553543039751168" + "templateHash": "13462616099297553465" }, "name": "Azure Synapse Analytics", "description": "This module deploys an Azure Synapse Analytics (Private Link Hub).", @@ -103,6 +103,154 @@ } }, "nullable": true + }, + "privateEndpointType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private endpoint." + } + }, + "location": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The location to deploy the private endpoint to." + } + }, + "service": { + "type": "string", + "metadata": { + "description": "Required. The service (sub-) type to deploy the private endpoint for. For example \"vault\" or \"blob\"." + } + }, + "subnetResourceId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the subnet where the endpoint needs to be created." + } + }, + "privateDnsZoneGroupName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided." + } + }, + "privateDnsZoneResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones." + } + }, + "customDnsConfigs": { + "type": "array", + "items": { + "type": "object", + "properties": { + "fqdn": { + "type": "string", + "nullable": true + }, + "ipAddresses": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. Custom DNS configurations." + } + }, + "ipConfigurations": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "groupId": { + "type": "string" + }, + "memberName": { + "type": "string" + }, + "privateIpAddress": { + "type": "string" + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints." + } + }, + "applicationSecurityGroupResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. Application security groups in which the private endpoint IP configuration is included." + } + }, + "customNetworkInterfaceName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The custom name of the network interface attached to the private endpoint." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "metadata": { + "description": "Optional. Specify the type of lock." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "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'." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags to be applied on all resources/resource groups in this deployment." + } + }, + "manualPrivateLinkServiceConnections": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. Manual PrivateLink Service Connections." + } + }, + "enableTelemetry": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + } + } + }, + "nullable": true } }, "parameters": { @@ -146,8 +294,7 @@ } }, "privateEndpoints": { - "type": "array", - "defaultValue": [], + "$ref": "#/definitions/privateEndpointType", "metadata": { "description": "Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible." } @@ -224,11 +371,11 @@ "privateLinkHub_privateEndpoints": { "copy": { "name": "privateLinkHub_privateEndpoints", - "count": "[length(parameters('privateEndpoints'))]" + "count": "[length(coalesce(parameters('privateEndpoints'), createArray()))]" }, "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", - "name": "[format('{0}-PrivateLinkHub-PrivateEndpoint-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "name": "[format('{0}-privateLinkHub-PrivateEndpoint-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", "properties": { "expressionEvaluationOptions": { "scope": "inner" @@ -237,32 +384,54 @@ "parameters": { "groupIds": { "value": [ - "[parameters('privateEndpoints')[copyIndex()].service]" + "[coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].service]" ] }, - "name": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'name'), createObject('value', parameters('privateEndpoints')[copyIndex()].name), createObject('value', format('pe-{0}-{1}-{2}', last(split(resourceId('Microsoft.Synapse/privateLinkHubs', parameters('name')), '/')), parameters('privateEndpoints')[copyIndex()].service, copyIndex())))]", + "name": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'name'), format('pep-{0}-{1}-{2}', last(split(resourceId('Microsoft.Synapse/privateLinkHubs', parameters('name')), '/')), coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].service), copyIndex()))]" + }, "serviceResourceId": { "value": "[resourceId('Microsoft.Synapse/privateLinkHubs', parameters('name'))]" }, "subnetResourceId": { - "value": "[parameters('privateEndpoints')[copyIndex()].subnetResourceId]" + "value": "[coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId]" }, "enableDefaultTelemetry": { - "value": "[variables('enableReferencedModulesTelemetry')]" + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'enableDefaultTelemetry'), variables('enableReferencedModulesTelemetry'))]" + }, + "location": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'location'), reference(split(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location)]" }, - "location": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'location'), createObject('value', parameters('privateEndpoints')[copyIndex()].location), createObject('value', reference(split(parameters('privateEndpoints')[copyIndex()].subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location))]", "lock": { - "value": "[coalesce(tryGet(parameters('privateEndpoints')[copyIndex()], 'lock'), parameters('lock'))]" - }, - "privateDnsZoneGroupName": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'privateDnsZoneGroupName'), createObject('value', parameters('privateEndpoints')[copyIndex()].privateDnsZoneGroupName), createObject('value', 'default'))]", - "privateDnsZoneResourceIds": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'privateDnsZoneResourceIds'), createObject('value', parameters('privateEndpoints')[copyIndex()].privateDnsZoneResourceIds), createObject('value', createArray()))]", - "roleAssignments": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'roleAssignments'), createObject('value', parameters('privateEndpoints')[copyIndex()].roleAssignments), createObject('value', createArray()))]", - "tags": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'tags'), createObject('value', parameters('privateEndpoints')[copyIndex()].tags), createObject('value', createObject()))]", - "manualPrivateLinkServiceConnections": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'manualPrivateLinkServiceConnections'), createObject('value', parameters('privateEndpoints')[copyIndex()].manualPrivateLinkServiceConnections), createObject('value', createArray()))]", - "customDnsConfigs": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'customDnsConfigs'), createObject('value', parameters('privateEndpoints')[copyIndex()].customDnsConfigs), createObject('value', createArray()))]", - "ipConfigurations": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'ipConfigurations'), createObject('value', parameters('privateEndpoints')[copyIndex()].ipConfigurations), createObject('value', createArray()))]", - "applicationSecurityGroupResourceIds": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'applicationSecurityGroupResourceIds'), createObject('value', parameters('privateEndpoints')[copyIndex()].applicationSecurityGroupResourceIds), createObject('value', createArray()))]", - "customNetworkInterfaceName": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'customNetworkInterfaceName'), createObject('value', parameters('privateEndpoints')[copyIndex()].customNetworkInterfaceName), createObject('value', ''))]" + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'lock'), parameters('lock'))]" + }, + "privateDnsZoneGroupName": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneGroupName')]" + }, + "privateDnsZoneResourceIds": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneResourceIds')]" + }, + "roleAssignments": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'roleAssignments')]" + }, + "tags": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'tags'), parameters('tags'))]" + }, + "manualPrivateLinkServiceConnections": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'manualPrivateLinkServiceConnections')]" + }, + "customDnsConfigs": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customDnsConfigs')]" + }, + "ipConfigurations": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'ipConfigurations')]" + }, + "applicationSecurityGroupResourceIds": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'applicationSecurityGroupResourceIds')]" + }, + "customNetworkInterfaceName": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customNetworkInterfaceName')]" + } }, "template": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", diff --git a/modules/synapse/workspace/.test/common/main.test.bicep b/modules/synapse/workspace/.test/common/main.test.bicep index 741e51171a..258596f817 100644 --- a/modules/synapse/workspace/.test/common/main.test.bicep +++ b/modules/synapse/workspace/.test/common/main.test.bicep @@ -86,9 +86,7 @@ module testDeployment '../../main.bicep' = { subnetResourceId: nestedDependencies.outputs.subnetResourceId service: 'SQL' privateDnsZoneResourceIds: [ - nestedDependencies.outputs.privateDNSZoneResourceId - ] tags: { 'hidden-title': 'This is visible in the resource name' diff --git a/modules/synapse/workspace/README.md b/modules/synapse/workspace/README.md index 868966dd69..a9af2ce3c1 100644 --- a/modules/synapse/workspace/README.md +++ b/modules/synapse/workspace/README.md @@ -785,7 +785,168 @@ Prevent Data Exfiltration. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible. - Required: No - Type: array -- Default: `[]` + + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`applicationSecurityGroupResourceIds`](#parameter-privateendpointsapplicationsecuritygroupresourceids) | No | array | Optional. Application security groups in which the private endpoint IP configuration is included. | +| [`customDnsConfigs`](#parameter-privateendpointscustomdnsconfigs) | No | array | Optional. Custom DNS configurations. | +| [`customNetworkInterfaceName`](#parameter-privateendpointscustomnetworkinterfacename) | No | string | Optional. The custom name of the network interface attached to the private endpoint. | +| [`enableTelemetry`](#parameter-privateendpointsenabletelemetry) | No | bool | Optional. Enable/Disable usage telemetry for module. | +| [`ipConfigurations`](#parameter-privateendpointsipconfigurations) | No | array | Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints. | +| [`location`](#parameter-privateendpointslocation) | No | string | Optional. The location to deploy the private endpoint to. | +| [`lock`](#parameter-privateendpointslock) | No | object | Optional. Specify the type of lock. | +| [`manualPrivateLinkServiceConnections`](#parameter-privateendpointsmanualprivatelinkserviceconnections) | No | array | Optional. Manual PrivateLink Service Connections. | +| [`name`](#parameter-privateendpointsname) | No | string | Optional. The name of the private endpoint. | +| [`privateDnsZoneGroupName`](#parameter-privateendpointsprivatednszonegroupname) | No | string | Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided. | +| [`privateDnsZoneResourceIds`](#parameter-privateendpointsprivatednszoneresourceids) | No | array | Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones. | +| [`roleAssignments`](#parameter-privateendpointsroleassignments) | No | array | 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'. | +| [`service`](#parameter-privateendpointsservice) | Yes | string | Required. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob". | +| [`subnetResourceId`](#parameter-privateendpointssubnetresourceid) | Yes | string | Required. Resource ID of the subnet where the endpoint needs to be created. | +| [`tags`](#parameter-privateendpointstags) | No | object | Optional. Tags to be applied on all resources/resource groups in this deployment. | + +### Parameter: `privateEndpoints.applicationSecurityGroupResourceIds` + +Optional. Application security groups in which the private endpoint IP configuration is included. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.customDnsConfigs` + +Optional. Custom DNS configurations. + +- Required: No +- Type: array + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`fqdn`](#parameter-privateendpointscustomdnsconfigsfqdn) | No | string | | +| [`ipAddresses`](#parameter-privateendpointscustomdnsconfigsipaddresses) | Yes | array | | + +### Parameter: `privateEndpoints.customDnsConfigs.fqdn` +- Required: No +- Type: string + +### Parameter: `privateEndpoints.customDnsConfigs.ipAddresses` +- Required: Yes +- Type: array + + +### Parameter: `privateEndpoints.customNetworkInterfaceName` + +Optional. The custom name of the network interface attached to the private endpoint. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.enableTelemetry` + +Optional. Enable/Disable usage telemetry for module. + +- Required: No +- Type: bool + +### Parameter: `privateEndpoints.ipConfigurations` + +Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints. + +- Required: No +- Type: array + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`groupId`](#parameter-privateendpointsipconfigurationsgroupid) | Yes | string | | +| [`memberName`](#parameter-privateendpointsipconfigurationsmembername) | Yes | string | | +| [`name`](#parameter-privateendpointsipconfigurationsname) | Yes | string | | +| [`privateIpAddress`](#parameter-privateendpointsipconfigurationsprivateipaddress) | Yes | string | | + +### Parameter: `privateEndpoints.ipConfigurations.groupId` +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.ipConfigurations.memberName` +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.ipConfigurations.name` +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.ipConfigurations.privateIpAddress` +- Required: Yes +- Type: string + + +### Parameter: `privateEndpoints.location` + +Optional. The location to deploy the private endpoint to. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.lock` + +Optional. Specify the type of lock. + +- Required: No +- Type: object + +### Parameter: `privateEndpoints.manualPrivateLinkServiceConnections` + +Optional. Manual PrivateLink Service Connections. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.name` + +Optional. The name of the private endpoint. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.privateDnsZoneGroupName` + +Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.privateDnsZoneResourceIds` + +Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.roleAssignments` + +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'. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.service` + +Required. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob". + +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.subnetResourceId` + +Required. Resource ID of the subnet where the endpoint needs to be created. + +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.tags` + +Optional. Tags to be applied on all resources/resource groups in this deployment. + +- Required: No +- Type: object ### Parameter: `publicNetworkAccess` diff --git a/modules/synapse/workspace/main.bicep b/modules/synapse/workspace/main.bicep index 08a39588db..d2616a828a 100644 --- a/modules/synapse/workspace/main.bicep +++ b/modules/synapse/workspace/main.bicep @@ -98,7 +98,7 @@ param lock lockType param roleAssignments roleAssignmentType @description('Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible.') -param privateEndpoints array = [] +param privateEndpoints privateEndpointType @description('Optional. Resource ID of the diagnostic storage account.') param diagnosticStorageAccountId string = '' @@ -297,27 +297,27 @@ resource workspace_roleAssignments 'Microsoft.Authorization/roleAssignments@2022 }] // Endpoints -module workspace_privateEndpoints '../../network/private-endpoint/main.bicep' = [for (privateEndpoint, index) in privateEndpoints: { - name: '${uniqueString(deployment().name, location)}-Workspace-PrivateEndpoint-${index}' +module workspace_privateEndpoints '../../network/private-endpoint/main.bicep' = [for (privateEndpoint, index) in (privateEndpoints ?? []): { + name: '${uniqueString(deployment().name, location)}-workspace-PrivateEndpoint-${index}' params: { groupIds: [ privateEndpoint.service ] - name: contains(privateEndpoint, 'name') ? privateEndpoint.name : 'pe-${last(split(workspace.id, '/'))}-${privateEndpoint.service}-${index}' + name: privateEndpoint.?name ?? 'pep-${last(split(workspace.id, '/'))}-${privateEndpoint.?service ?? privateEndpoint.service}-${index}' serviceResourceId: workspace.id subnetResourceId: privateEndpoint.subnetResourceId - enableDefaultTelemetry: enableReferencedModulesTelemetry - location: contains(privateEndpoint, 'location') ? privateEndpoint.location : reference(split(privateEndpoint.subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location + enableDefaultTelemetry: privateEndpoint.?enableDefaultTelemetry ?? enableReferencedModulesTelemetry + location: privateEndpoint.?location ?? reference(split(privateEndpoint.subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location lock: privateEndpoint.?lock ?? lock - privateDnsZoneGroupName: contains(privateEndpoint, 'privateDnsZoneGroupName') ? privateEndpoint.privateDnsZoneGroupName : 'default' - privateDnsZoneResourceIds: contains(privateEndpoint, 'privateDnsZoneResourceIds') ? privateEndpoint.privateDnsZoneResourceIds : [] - 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 : [] - applicationSecurityGroupResourceIds: contains(privateEndpoint, 'applicationSecurityGroupResourceIds') ? privateEndpoint.applicationSecurityGroupResourceIds : [] - customNetworkInterfaceName: contains(privateEndpoint, 'customNetworkInterfaceName') ? privateEndpoint.customNetworkInterfaceName : '' + privateDnsZoneGroupName: privateEndpoint.?privateDnsZoneGroupName + privateDnsZoneResourceIds: privateEndpoint.?privateDnsZoneResourceIds + roleAssignments: privateEndpoint.?roleAssignments + tags: privateEndpoint.?tags ?? tags + manualPrivateLinkServiceConnections: privateEndpoint.?manualPrivateLinkServiceConnections + customDnsConfigs: privateEndpoint.?customDnsConfigs + ipConfigurations: privateEndpoint.?ipConfigurations + applicationSecurityGroupResourceIds: privateEndpoint.?applicationSecurityGroupResourceIds + customNetworkInterfaceName: privateEndpoint.?customNetworkInterfaceName } }] @@ -386,3 +386,58 @@ type roleAssignmentType = { @description('Optional. The Resource Id of the delegated managed identity resource.') delegatedManagedIdentityResourceId: string? }[]? + +type privateEndpointType = { + @description('Optional. The name of the private endpoint.') + name: string? + + @description('Optional. The location to deploy the private endpoint to.') + location: string? + + @description('Required. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob".') + service: string + + @description('Required. Resource ID of the subnet where the endpoint needs to be created.') + subnetResourceId: string + + @description('Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided.') + privateDnsZoneGroupName: string? + + @description('Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones.') + privateDnsZoneResourceIds: string[]? + + @description('Optional. Custom DNS configurations.') + customDnsConfigs: { + fqdn: string? + ipAddresses: string[] + }[]? + + @description('Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints.') + ipConfigurations: { + name: string + groupId: string + memberName: string + privateIpAddress: string + }[]? + + @description('Optional. Application security groups in which the private endpoint IP configuration is included.') + applicationSecurityGroupResourceIds: string[]? + + @description('Optional. The custom name of the network interface attached to the private endpoint.') + customNetworkInterfaceName: string? + + @description('Optional. Specify the type of lock.') + lock: lockType + + @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\'.') + roleAssignments: roleAssignmentType + + @description('Optional. Tags to be applied on all resources/resource groups in this deployment.') + tags: object? + + @description('Optional. Manual PrivateLink Service Connections.') + manualPrivateLinkServiceConnections: array? + + @description('Optional. Enable/Disable usage telemetry for module.') + enableTelemetry: bool? +}[]? diff --git a/modules/synapse/workspace/main.json b/modules/synapse/workspace/main.json index a772b190e3..677555b5c2 100644 --- a/modules/synapse/workspace/main.json +++ b/modules/synapse/workspace/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.22.6.54827", - "templateHash": "10923669375290685211" + "templateHash": "12758052897750463428" }, "name": "Synapse Workspaces", "description": "This module deploys a Synapse Workspace.", @@ -103,6 +103,154 @@ } }, "nullable": true + }, + "privateEndpointType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private endpoint." + } + }, + "location": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The location to deploy the private endpoint to." + } + }, + "service": { + "type": "string", + "metadata": { + "description": "Required. The service (sub-) type to deploy the private endpoint for. For example \"vault\" or \"blob\"." + } + }, + "subnetResourceId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the subnet where the endpoint needs to be created." + } + }, + "privateDnsZoneGroupName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided." + } + }, + "privateDnsZoneResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones." + } + }, + "customDnsConfigs": { + "type": "array", + "items": { + "type": "object", + "properties": { + "fqdn": { + "type": "string", + "nullable": true + }, + "ipAddresses": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. Custom DNS configurations." + } + }, + "ipConfigurations": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "groupId": { + "type": "string" + }, + "memberName": { + "type": "string" + }, + "privateIpAddress": { + "type": "string" + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints." + } + }, + "applicationSecurityGroupResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. Application security groups in which the private endpoint IP configuration is included." + } + }, + "customNetworkInterfaceName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The custom name of the network interface attached to the private endpoint." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "metadata": { + "description": "Optional. Specify the type of lock." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "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'." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags to be applied on all resources/resource groups in this deployment." + } + }, + "manualPrivateLinkServiceConnections": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. Manual PrivateLink Service Connections." + } + }, + "enableTelemetry": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + } + } + }, + "nullable": true } }, "parameters": { @@ -310,8 +458,7 @@ } }, "privateEndpoints": { - "type": "array", - "defaultValue": [], + "$ref": "#/definitions/privateEndpointType", "metadata": { "description": "Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible." } @@ -867,11 +1014,11 @@ "workspace_privateEndpoints": { "copy": { "name": "workspace_privateEndpoints", - "count": "[length(parameters('privateEndpoints'))]" + "count": "[length(coalesce(parameters('privateEndpoints'), createArray()))]" }, "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", - "name": "[format('{0}-Workspace-PrivateEndpoint-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "name": "[format('{0}-workspace-PrivateEndpoint-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", "properties": { "expressionEvaluationOptions": { "scope": "inner" @@ -880,32 +1027,54 @@ "parameters": { "groupIds": { "value": [ - "[parameters('privateEndpoints')[copyIndex()].service]" + "[coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].service]" ] }, - "name": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'name'), createObject('value', parameters('privateEndpoints')[copyIndex()].name), createObject('value', format('pe-{0}-{1}-{2}', last(split(resourceId('Microsoft.Synapse/workspaces', parameters('name')), '/')), parameters('privateEndpoints')[copyIndex()].service, copyIndex())))]", + "name": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'name'), format('pep-{0}-{1}-{2}', last(split(resourceId('Microsoft.Synapse/workspaces', parameters('name')), '/')), coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].service), copyIndex()))]" + }, "serviceResourceId": { "value": "[resourceId('Microsoft.Synapse/workspaces', parameters('name'))]" }, "subnetResourceId": { - "value": "[parameters('privateEndpoints')[copyIndex()].subnetResourceId]" + "value": "[coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId]" }, "enableDefaultTelemetry": { - "value": "[variables('enableReferencedModulesTelemetry')]" + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'enableDefaultTelemetry'), variables('enableReferencedModulesTelemetry'))]" + }, + "location": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'location'), reference(split(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location)]" }, - "location": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'location'), createObject('value', parameters('privateEndpoints')[copyIndex()].location), createObject('value', reference(split(parameters('privateEndpoints')[copyIndex()].subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location))]", "lock": { - "value": "[coalesce(tryGet(parameters('privateEndpoints')[copyIndex()], 'lock'), parameters('lock'))]" + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'lock'), parameters('lock'))]" + }, + "privateDnsZoneGroupName": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneGroupName')]" + }, + "privateDnsZoneResourceIds": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneResourceIds')]" + }, + "roleAssignments": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'roleAssignments')]" }, - "privateDnsZoneGroupName": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'privateDnsZoneGroupName'), createObject('value', parameters('privateEndpoints')[copyIndex()].privateDnsZoneGroupName), createObject('value', 'default'))]", - "privateDnsZoneResourceIds": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'privateDnsZoneResourceIds'), createObject('value', parameters('privateEndpoints')[copyIndex()].privateDnsZoneResourceIds), createObject('value', createArray()))]", - "roleAssignments": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'roleAssignments'), createObject('value', parameters('privateEndpoints')[copyIndex()].roleAssignments), createObject('value', createArray()))]", - "tags": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'tags'), createObject('value', parameters('privateEndpoints')[copyIndex()].tags), createObject('value', createObject()))]", - "manualPrivateLinkServiceConnections": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'manualPrivateLinkServiceConnections'), createObject('value', parameters('privateEndpoints')[copyIndex()].manualPrivateLinkServiceConnections), createObject('value', createArray()))]", - "customDnsConfigs": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'customDnsConfigs'), createObject('value', parameters('privateEndpoints')[copyIndex()].customDnsConfigs), createObject('value', createArray()))]", - "ipConfigurations": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'ipConfigurations'), createObject('value', parameters('privateEndpoints')[copyIndex()].ipConfigurations), createObject('value', createArray()))]", - "applicationSecurityGroupResourceIds": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'applicationSecurityGroupResourceIds'), createObject('value', parameters('privateEndpoints')[copyIndex()].applicationSecurityGroupResourceIds), createObject('value', createArray()))]", - "customNetworkInterfaceName": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'customNetworkInterfaceName'), createObject('value', parameters('privateEndpoints')[copyIndex()].customNetworkInterfaceName), createObject('value', ''))]" + "tags": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'tags'), parameters('tags'))]" + }, + "manualPrivateLinkServiceConnections": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'manualPrivateLinkServiceConnections')]" + }, + "customDnsConfigs": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customDnsConfigs')]" + }, + "ipConfigurations": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'ipConfigurations')]" + }, + "applicationSecurityGroupResourceIds": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'applicationSecurityGroupResourceIds')]" + }, + "customNetworkInterfaceName": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customNetworkInterfaceName')]" + } }, "template": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", diff --git a/modules/web/site/.test/functionAppCommon/main.test.bicep b/modules/web/site/.test/functionAppCommon/main.test.bicep index efcf051533..9948b9688f 100644 --- a/modules/web/site/.test/functionAppCommon/main.test.bicep +++ b/modules/web/site/.test/functionAppCommon/main.test.bicep @@ -150,7 +150,6 @@ module testDeployment '../../main.bicep' = { } privateEndpoints: [ { - service: 'sites' subnetResourceId: nestedDependencies.outputs.subnetResourceId privateDnsZoneResourceIds: [ nestedDependencies.outputs.privateDNSZoneResourceId diff --git a/modules/web/site/.test/webAppCommon/main.test.bicep b/modules/web/site/.test/webAppCommon/main.test.bicep index 5f80c1b05d..c4d9ff9bb9 100644 --- a/modules/web/site/.test/webAppCommon/main.test.bicep +++ b/modules/web/site/.test/webAppCommon/main.test.bicep @@ -86,7 +86,6 @@ module testDeployment '../../main.bicep' = { diagnosticEventHubName: diagnosticDependencies.outputs.eventHubNamespaceEventHubName privateEndpoints: [ { - service: 'sites' subnetResourceId: nestedDependencies.outputs.subnetResourceId privateDnsZoneResourceIds: [ @@ -129,7 +128,6 @@ module testDeployment '../../main.bicep' = { ] privateEndpoints: [ { - service: 'sites' subnetResourceId: nestedDependencies.outputs.subnetResourceId privateDnsZoneResourceIds: [ nestedDependencies.outputs.privateDNSZoneResourceId diff --git a/modules/web/site/README.md b/modules/web/site/README.md index d679f188df..f748d00c85 100644 --- a/modules/web/site/README.md +++ b/modules/web/site/README.md @@ -146,7 +146,6 @@ module site 'br:bicep/modules/web.site:1.0.0' = { privateDnsZoneResourceIds: [ '' ] - service: 'sites' subnetResourceId: '' tags: { Environment: 'Non-Prod' @@ -312,7 +311,6 @@ module site 'br:bicep/modules/web.site:1.0.0' = { "privateDnsZoneResourceIds": [ "" ], - "service": "sites", "subnetResourceId": "", "tags": { "Environment": "Non-Prod", @@ -463,7 +461,6 @@ module site 'br:bicep/modules/web.site:1.0.0' = { privateDnsZoneResourceIds: [ '' ] - service: 'sites' subnetResourceId: '' tags: { Environment: 'Non-Prod' @@ -508,7 +505,6 @@ module site 'br:bicep/modules/web.site:1.0.0' = { privateDnsZoneResourceIds: [ '' ] - service: 'sites' subnetResourceId: '' tags: { Environment: 'Non-Prod' @@ -620,7 +616,6 @@ module site 'br:bicep/modules/web.site:1.0.0' = { "privateDnsZoneResourceIds": [ "" ], - "service": "sites", "subnetResourceId": "", "tags": { "Environment": "Non-Prod", @@ -675,7 +670,6 @@ module site 'br:bicep/modules/web.site:1.0.0' = { "privateDnsZoneResourceIds": [ "" ], - "service": "sites", "subnetResourceId": "", "tags": { "Environment": "Non-Prod", @@ -1089,7 +1083,168 @@ Name of the site. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible. - Required: No - Type: array -- Default: `[]` + + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`applicationSecurityGroupResourceIds`](#parameter-privateendpointsapplicationsecuritygroupresourceids) | No | array | Optional. Application security groups in which the private endpoint IP configuration is included. | +| [`customDnsConfigs`](#parameter-privateendpointscustomdnsconfigs) | No | array | Optional. Custom DNS configurations. | +| [`customNetworkInterfaceName`](#parameter-privateendpointscustomnetworkinterfacename) | No | string | Optional. The custom name of the network interface attached to the private endpoint. | +| [`enableTelemetry`](#parameter-privateendpointsenabletelemetry) | No | bool | Optional. Enable/Disable usage telemetry for module. | +| [`ipConfigurations`](#parameter-privateendpointsipconfigurations) | No | array | Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints. | +| [`location`](#parameter-privateendpointslocation) | No | string | Optional. The location to deploy the private endpoint to. | +| [`lock`](#parameter-privateendpointslock) | No | object | Optional. Specify the type of lock. | +| [`manualPrivateLinkServiceConnections`](#parameter-privateendpointsmanualprivatelinkserviceconnections) | No | array | Optional. Manual PrivateLink Service Connections. | +| [`name`](#parameter-privateendpointsname) | No | string | Optional. The name of the private endpoint. | +| [`privateDnsZoneGroupName`](#parameter-privateendpointsprivatednszonegroupname) | No | string | Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided. | +| [`privateDnsZoneResourceIds`](#parameter-privateendpointsprivatednszoneresourceids) | No | array | Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones. | +| [`roleAssignments`](#parameter-privateendpointsroleassignments) | No | array | 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'. | +| [`service`](#parameter-privateendpointsservice) | No | string | Optional. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob". | +| [`subnetResourceId`](#parameter-privateendpointssubnetresourceid) | Yes | string | Required. Resource ID of the subnet where the endpoint needs to be created. | +| [`tags`](#parameter-privateendpointstags) | No | object | Optional. Tags to be applied on all resources/resource groups in this deployment. | + +### Parameter: `privateEndpoints.applicationSecurityGroupResourceIds` + +Optional. Application security groups in which the private endpoint IP configuration is included. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.customDnsConfigs` + +Optional. Custom DNS configurations. + +- Required: No +- Type: array + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`fqdn`](#parameter-privateendpointscustomdnsconfigsfqdn) | No | string | | +| [`ipAddresses`](#parameter-privateendpointscustomdnsconfigsipaddresses) | Yes | array | | + +### Parameter: `privateEndpoints.customDnsConfigs.fqdn` +- Required: No +- Type: string + +### Parameter: `privateEndpoints.customDnsConfigs.ipAddresses` +- Required: Yes +- Type: array + + +### Parameter: `privateEndpoints.customNetworkInterfaceName` + +Optional. The custom name of the network interface attached to the private endpoint. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.enableTelemetry` + +Optional. Enable/Disable usage telemetry for module. + +- Required: No +- Type: bool + +### Parameter: `privateEndpoints.ipConfigurations` + +Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints. + +- Required: No +- Type: array + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`groupId`](#parameter-privateendpointsipconfigurationsgroupid) | Yes | string | | +| [`memberName`](#parameter-privateendpointsipconfigurationsmembername) | Yes | string | | +| [`name`](#parameter-privateendpointsipconfigurationsname) | Yes | string | | +| [`privateIpAddress`](#parameter-privateendpointsipconfigurationsprivateipaddress) | Yes | string | | + +### Parameter: `privateEndpoints.ipConfigurations.groupId` +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.ipConfigurations.memberName` +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.ipConfigurations.name` +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.ipConfigurations.privateIpAddress` +- Required: Yes +- Type: string + + +### Parameter: `privateEndpoints.location` + +Optional. The location to deploy the private endpoint to. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.lock` + +Optional. Specify the type of lock. + +- Required: No +- Type: object + +### Parameter: `privateEndpoints.manualPrivateLinkServiceConnections` + +Optional. Manual PrivateLink Service Connections. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.name` + +Optional. The name of the private endpoint. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.privateDnsZoneGroupName` + +Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.privateDnsZoneResourceIds` + +Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.roleAssignments` + +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'. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.service` + +Optional. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob". + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.subnetResourceId` + +Required. Resource ID of the subnet where the endpoint needs to be created. + +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.tags` + +Optional. Tags to be applied on all resources/resource groups in this deployment. + +- Required: No +- Type: object ### Parameter: `publicNetworkAccess` diff --git a/modules/web/site/main.bicep b/modules/web/site/main.bicep index 0964c9a5db..8dcd8df3dd 100644 --- a/modules/web/site/main.bicep +++ b/modules/web/site/main.bicep @@ -79,7 +79,7 @@ param authSettingV2Configuration object = {} param lock lockType @description('Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible.') -param privateEndpoints array = [] +param privateEndpoints privateEndpointType @description('Optional. Configuration for deployment slots for an app.') param slots array = [] @@ -415,27 +415,27 @@ resource app_roleAssignments 'Microsoft.Authorization/roleAssignments@2022-04-01 scope: app }] -module app_privateEndpoints '../../network/private-endpoint/main.bicep' = [for (privateEndpoint, index) in privateEndpoints: { - name: '${uniqueString(deployment().name, location)}-Site-PrivateEndpoint-${index}' +module app_privateEndpoints '../../network/private-endpoint/main.bicep' = [for (privateEndpoint, index) in (privateEndpoints ?? []): { + name: '${uniqueString(deployment().name, location)}-app-PrivateEndpoint-${index}' params: { groupIds: [ - privateEndpoint.service + privateEndpoint.?service ?? 'sites' ] - name: contains(privateEndpoint, 'name') ? privateEndpoint.name : 'pe-${last(split(app.id, '/'))}-${privateEndpoint.service}-${index}' + name: privateEndpoint.?name ?? 'pep-${last(split(app.id, '/'))}-${privateEndpoint.?service ?? 'sites'}-${index}' serviceResourceId: app.id subnetResourceId: privateEndpoint.subnetResourceId - enableDefaultTelemetry: enableReferencedModulesTelemetry - location: contains(privateEndpoint, 'location') ? privateEndpoint.location : reference(split(privateEndpoint.subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location + enableDefaultTelemetry: privateEndpoint.?enableDefaultTelemetry ?? enableReferencedModulesTelemetry + location: privateEndpoint.?location ?? reference(split(privateEndpoint.subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location lock: privateEndpoint.?lock ?? lock - privateDnsZoneGroupName: contains(privateEndpoint, 'privateDnsZoneGroupName') ? privateEndpoint.privateDnsZoneGroupName : 'default' - privateDnsZoneResourceIds: contains(privateEndpoint, 'privateDnsZoneResourceIds') ? privateEndpoint.privateDnsZoneResourceIds : [] - 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 : [] - applicationSecurityGroupResourceIds: contains(privateEndpoint, 'applicationSecurityGroupResourceIds') ? privateEndpoint.applicationSecurityGroupResourceIds : [] - customNetworkInterfaceName: contains(privateEndpoint, 'customNetworkInterfaceName') ? privateEndpoint.customNetworkInterfaceName : '' + privateDnsZoneGroupName: privateEndpoint.?privateDnsZoneGroupName + privateDnsZoneResourceIds: privateEndpoint.?privateDnsZoneResourceIds + roleAssignments: privateEndpoint.?roleAssignments + tags: privateEndpoint.?tags ?? tags + manualPrivateLinkServiceConnections: privateEndpoint.?manualPrivateLinkServiceConnections + customDnsConfigs: privateEndpoint.?customDnsConfigs + ipConfigurations: privateEndpoint.?ipConfigurations + applicationSecurityGroupResourceIds: privateEndpoint.?applicationSecurityGroupResourceIds + customNetworkInterfaceName: privateEndpoint.?customNetworkInterfaceName } }] @@ -500,3 +500,58 @@ type roleAssignmentType = { @description('Optional. The Resource Id of the delegated managed identity resource.') delegatedManagedIdentityResourceId: string? }[]? + +type privateEndpointType = { + @description('Optional. The name of the private endpoint.') + name: string? + + @description('Optional. The location to deploy the private endpoint to.') + location: string? + + @description('Optional. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob".') + service: string? + + @description('Required. Resource ID of the subnet where the endpoint needs to be created.') + subnetResourceId: string + + @description('Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided.') + privateDnsZoneGroupName: string? + + @description('Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones.') + privateDnsZoneResourceIds: string[]? + + @description('Optional. Custom DNS configurations.') + customDnsConfigs: { + fqdn: string? + ipAddresses: string[] + }[]? + + @description('Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints.') + ipConfigurations: { + name: string + groupId: string + memberName: string + privateIpAddress: string + }[]? + + @description('Optional. Application security groups in which the private endpoint IP configuration is included.') + applicationSecurityGroupResourceIds: string[]? + + @description('Optional. The custom name of the network interface attached to the private endpoint.') + customNetworkInterfaceName: string? + + @description('Optional. Specify the type of lock.') + lock: lockType + + @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\'.') + roleAssignments: roleAssignmentType + + @description('Optional. Tags to be applied on all resources/resource groups in this deployment.') + tags: object? + + @description('Optional. Manual PrivateLink Service Connections.') + manualPrivateLinkServiceConnections: array? + + @description('Optional. Enable/Disable usage telemetry for module.') + enableTelemetry: bool? +}[]? diff --git a/modules/web/site/main.json b/modules/web/site/main.json index 84a1a15880..1bbe74f580 100644 --- a/modules/web/site/main.json +++ b/modules/web/site/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.22.6.54827", - "templateHash": "249993900851794447" + "templateHash": "4843779677918580425" }, "name": "Web/Function Apps", "description": "This module deploys a Web or Function App.", @@ -103,6 +103,155 @@ } }, "nullable": true + }, + "privateEndpointType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private endpoint." + } + }, + "location": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The location to deploy the private endpoint to." + } + }, + "service": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The service (sub-) type to deploy the private endpoint for. For example \"vault\" or \"blob\"." + } + }, + "subnetResourceId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the subnet where the endpoint needs to be created." + } + }, + "privateDnsZoneGroupName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided." + } + }, + "privateDnsZoneResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones." + } + }, + "customDnsConfigs": { + "type": "array", + "items": { + "type": "object", + "properties": { + "fqdn": { + "type": "string", + "nullable": true + }, + "ipAddresses": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. Custom DNS configurations." + } + }, + "ipConfigurations": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "groupId": { + "type": "string" + }, + "memberName": { + "type": "string" + }, + "privateIpAddress": { + "type": "string" + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints." + } + }, + "applicationSecurityGroupResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. Application security groups in which the private endpoint IP configuration is included." + } + }, + "customNetworkInterfaceName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The custom name of the network interface attached to the private endpoint." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "metadata": { + "description": "Optional. Specify the type of lock." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "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'." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags to be applied on all resources/resource groups in this deployment." + } + }, + "manualPrivateLinkServiceConnections": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. Manual PrivateLink Service Connections." + } + }, + "enableTelemetry": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + } + } + }, + "nullable": true } }, "parameters": { @@ -271,8 +420,7 @@ } }, "privateEndpoints": { - "type": "array", - "defaultValue": [], + "$ref": "#/definitions/privateEndpointType", "metadata": { "description": "Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible." } @@ -986,7 +1134,7 @@ "_generator": { "name": "bicep", "version": "0.22.6.54827", - "templateHash": "11020134105665438870" + "templateHash": "17150701166857849727" }, "name": "Web/Function App Deployment Slots", "description": "This module deploys a Web or Function App Deployment Slot.", @@ -1083,6 +1231,155 @@ } }, "nullable": true + }, + "privateEndpointType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private endpoint." + } + }, + "location": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The location to deploy the private endpoint to." + } + }, + "service": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The service (sub-) type to deploy the private endpoint for. For example \"vault\" or \"blob\"." + } + }, + "subnetResourceId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the subnet where the endpoint needs to be created." + } + }, + "privateDnsZoneGroupName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided." + } + }, + "privateDnsZoneResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones." + } + }, + "customDnsConfigs": { + "type": "array", + "items": { + "type": "object", + "properties": { + "fqdn": { + "type": "string", + "nullable": true + }, + "ipAddresses": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. Custom DNS configurations." + } + }, + "ipConfigurations": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "groupId": { + "type": "string" + }, + "memberName": { + "type": "string" + }, + "privateIpAddress": { + "type": "string" + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints." + } + }, + "applicationSecurityGroupResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. Application security groups in which the private endpoint IP configuration is included." + } + }, + "customNetworkInterfaceName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The custom name of the network interface attached to the private endpoint." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "metadata": { + "description": "Optional. Specify the type of lock." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "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'." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags to be applied on all resources/resource groups in this deployment." + } + }, + "manualPrivateLinkServiceConnections": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. Manual PrivateLink Service Connections." + } + }, + "enableTelemetry": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + } + } + }, + "nullable": true } }, "parameters": { @@ -1230,8 +1527,7 @@ } }, "privateEndpoints": { - "type": "array", - "defaultValue": [], + "$ref": "#/definitions/privateEndpointType", "metadata": { "description": "Optional. Configuration details for private endpoints." } @@ -2038,11 +2334,11 @@ "slot_privateEndpoints": { "copy": { "name": "slot_privateEndpoints", - "count": "[length(parameters('privateEndpoints'))]" + "count": "[length(coalesce(parameters('privateEndpoints'), createArray()))]" }, "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", - "name": "[format('{0}-Slot-{1}-PrivateEndpoint-{2}', uniqueString(deployment().name, parameters('location')), parameters('name'), copyIndex())]", + "name": "[format('{0}-app-PrivateEndpoint-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", "properties": { "expressionEvaluationOptions": { "scope": "inner" @@ -2051,29 +2347,54 @@ "parameters": { "groupIds": { "value": [ - "[format('{0}-{1}', parameters('privateEndpoints')[copyIndex()].service, parameters('name'))]" + "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'sites')]" ] }, - "name": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'name'), createObject('value', parameters('privateEndpoints')[copyIndex()].name), createObject('value', format('pe-{0}-{1}-{2}', last(split(resourceId('Microsoft.Web/sites/slots', parameters('appName'), parameters('name')), '/')), parameters('privateEndpoints')[copyIndex()].service, copyIndex())))]", + "name": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'name'), format('pep-{0}-{1}-{2}', last(split(resourceId('Microsoft.Web/sites', parameters('appName')), '/')), coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'sites'), copyIndex()))]" + }, "serviceResourceId": { "value": "[resourceId('Microsoft.Web/sites', parameters('appName'))]" }, "subnetResourceId": { - "value": "[parameters('privateEndpoints')[copyIndex()].subnetResourceId]" + "value": "[coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId]" }, "enableDefaultTelemetry": { - "value": "[variables('enableReferencedModulesTelemetry')]" + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'enableDefaultTelemetry'), variables('enableReferencedModulesTelemetry'))]" + }, + "location": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'location'), reference(split(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location)]" }, - "location": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'location'), createObject('value', parameters('privateEndpoints')[copyIndex()].location), createObject('value', reference(split(parameters('privateEndpoints')[copyIndex()].subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location))]", "lock": { - "value": "[coalesce(tryGet(parameters('privateEndpoints')[copyIndex()], 'lock'), parameters('lock'))]" - }, - "privateDnsZoneGroupName": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'privateDnsZoneGroupName'), createObject('value', parameters('privateEndpoints')[copyIndex()].privateDnsZoneGroupName), createObject('value', 'default'))]", - "privateDnsZoneResourceIds": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'privateDnsZoneResourceIds'), createObject('value', parameters('privateEndpoints')[copyIndex()].privateDnsZoneResourceIds), createObject('value', createArray()))]", - "roleAssignments": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'roleAssignments'), createObject('value', parameters('privateEndpoints')[copyIndex()].roleAssignments), createObject('value', createArray()))]", - "tags": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'tags'), createObject('value', parameters('privateEndpoints')[copyIndex()].tags), createObject('value', createObject()))]", - "manualPrivateLinkServiceConnections": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'manualPrivateLinkServiceConnections'), createObject('value', parameters('privateEndpoints')[copyIndex()].manualPrivateLinkServiceConnections), createObject('value', createArray()))]", - "customDnsConfigs": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'customDnsConfigs'), createObject('value', parameters('privateEndpoints')[copyIndex()].customDnsConfigs), createObject('value', createArray()))]" + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'lock'), parameters('lock'))]" + }, + "privateDnsZoneGroupName": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneGroupName')]" + }, + "privateDnsZoneResourceIds": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneResourceIds')]" + }, + "roleAssignments": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'roleAssignments')]" + }, + "tags": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'tags'), parameters('tags'))]" + }, + "manualPrivateLinkServiceConnections": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'manualPrivateLinkServiceConnections')]" + }, + "customDnsConfigs": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customDnsConfigs')]" + }, + "ipConfigurations": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'ipConfigurations')]" + }, + "applicationSecurityGroupResourceIds": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'applicationSecurityGroupResourceIds')]" + }, + "customNetworkInterfaceName": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customNetworkInterfaceName')]" + } }, "template": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", @@ -2558,8 +2879,7 @@ } }, "dependsOn": [ - "app", - "slot" + "app" ] } }, @@ -2872,11 +3192,11 @@ "app_privateEndpoints": { "copy": { "name": "app_privateEndpoints", - "count": "[length(parameters('privateEndpoints'))]" + "count": "[length(coalesce(parameters('privateEndpoints'), createArray()))]" }, "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", - "name": "[format('{0}-Site-PrivateEndpoint-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "name": "[format('{0}-app-PrivateEndpoint-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", "properties": { "expressionEvaluationOptions": { "scope": "inner" @@ -2885,32 +3205,54 @@ "parameters": { "groupIds": { "value": [ - "[parameters('privateEndpoints')[copyIndex()].service]" + "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'sites')]" ] }, - "name": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'name'), createObject('value', parameters('privateEndpoints')[copyIndex()].name), createObject('value', format('pe-{0}-{1}-{2}', last(split(resourceId('Microsoft.Web/sites', parameters('name')), '/')), parameters('privateEndpoints')[copyIndex()].service, copyIndex())))]", + "name": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'name'), format('pep-{0}-{1}-{2}', last(split(resourceId('Microsoft.Web/sites', parameters('name')), '/')), coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'sites'), copyIndex()))]" + }, "serviceResourceId": { "value": "[resourceId('Microsoft.Web/sites', parameters('name'))]" }, "subnetResourceId": { - "value": "[parameters('privateEndpoints')[copyIndex()].subnetResourceId]" + "value": "[coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId]" }, "enableDefaultTelemetry": { - "value": "[variables('enableReferencedModulesTelemetry')]" + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'enableDefaultTelemetry'), variables('enableReferencedModulesTelemetry'))]" + }, + "location": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'location'), reference(split(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location)]" }, - "location": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'location'), createObject('value', parameters('privateEndpoints')[copyIndex()].location), createObject('value', reference(split(parameters('privateEndpoints')[copyIndex()].subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location))]", "lock": { - "value": "[coalesce(tryGet(parameters('privateEndpoints')[copyIndex()], 'lock'), parameters('lock'))]" - }, - "privateDnsZoneGroupName": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'privateDnsZoneGroupName'), createObject('value', parameters('privateEndpoints')[copyIndex()].privateDnsZoneGroupName), createObject('value', 'default'))]", - "privateDnsZoneResourceIds": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'privateDnsZoneResourceIds'), createObject('value', parameters('privateEndpoints')[copyIndex()].privateDnsZoneResourceIds), createObject('value', createArray()))]", - "roleAssignments": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'roleAssignments'), createObject('value', parameters('privateEndpoints')[copyIndex()].roleAssignments), createObject('value', createArray()))]", - "tags": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'tags'), createObject('value', parameters('privateEndpoints')[copyIndex()].tags), createObject('value', createObject()))]", - "manualPrivateLinkServiceConnections": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'manualPrivateLinkServiceConnections'), createObject('value', parameters('privateEndpoints')[copyIndex()].manualPrivateLinkServiceConnections), createObject('value', createArray()))]", - "customDnsConfigs": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'customDnsConfigs'), createObject('value', parameters('privateEndpoints')[copyIndex()].customDnsConfigs), createObject('value', createArray()))]", - "ipConfigurations": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'ipConfigurations'), createObject('value', parameters('privateEndpoints')[copyIndex()].ipConfigurations), createObject('value', createArray()))]", - "applicationSecurityGroupResourceIds": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'applicationSecurityGroupResourceIds'), createObject('value', parameters('privateEndpoints')[copyIndex()].applicationSecurityGroupResourceIds), createObject('value', createArray()))]", - "customNetworkInterfaceName": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'customNetworkInterfaceName'), createObject('value', parameters('privateEndpoints')[copyIndex()].customNetworkInterfaceName), createObject('value', ''))]" + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'lock'), parameters('lock'))]" + }, + "privateDnsZoneGroupName": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneGroupName')]" + }, + "privateDnsZoneResourceIds": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneResourceIds')]" + }, + "roleAssignments": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'roleAssignments')]" + }, + "tags": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'tags'), parameters('tags'))]" + }, + "manualPrivateLinkServiceConnections": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'manualPrivateLinkServiceConnections')]" + }, + "customDnsConfigs": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customDnsConfigs')]" + }, + "ipConfigurations": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'ipConfigurations')]" + }, + "applicationSecurityGroupResourceIds": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'applicationSecurityGroupResourceIds')]" + }, + "customNetworkInterfaceName": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customNetworkInterfaceName')]" + } }, "template": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", diff --git a/modules/web/site/slot/README.md b/modules/web/site/slot/README.md index 0fc2fec8d0..d31c9f3387 100644 --- a/modules/web/site/slot/README.md +++ b/modules/web/site/slot/README.md @@ -330,7 +330,168 @@ Name of the slot. Configuration details for private endpoints. - Required: No - Type: array -- Default: `[]` + + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`applicationSecurityGroupResourceIds`](#parameter-privateendpointsapplicationsecuritygroupresourceids) | No | array | Optional. Application security groups in which the private endpoint IP configuration is included. | +| [`customDnsConfigs`](#parameter-privateendpointscustomdnsconfigs) | No | array | Optional. Custom DNS configurations. | +| [`customNetworkInterfaceName`](#parameter-privateendpointscustomnetworkinterfacename) | No | string | Optional. The custom name of the network interface attached to the private endpoint. | +| [`enableTelemetry`](#parameter-privateendpointsenabletelemetry) | No | bool | Optional. Enable/Disable usage telemetry for module. | +| [`ipConfigurations`](#parameter-privateendpointsipconfigurations) | No | array | Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints. | +| [`location`](#parameter-privateendpointslocation) | No | string | Optional. The location to deploy the private endpoint to. | +| [`lock`](#parameter-privateendpointslock) | No | object | Optional. Specify the type of lock. | +| [`manualPrivateLinkServiceConnections`](#parameter-privateendpointsmanualprivatelinkserviceconnections) | No | array | Optional. Manual PrivateLink Service Connections. | +| [`name`](#parameter-privateendpointsname) | No | string | Optional. The name of the private endpoint. | +| [`privateDnsZoneGroupName`](#parameter-privateendpointsprivatednszonegroupname) | No | string | Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided. | +| [`privateDnsZoneResourceIds`](#parameter-privateendpointsprivatednszoneresourceids) | No | array | Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones. | +| [`roleAssignments`](#parameter-privateendpointsroleassignments) | No | array | 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'. | +| [`service`](#parameter-privateendpointsservice) | No | string | Optional. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob". | +| [`subnetResourceId`](#parameter-privateendpointssubnetresourceid) | Yes | string | Required. Resource ID of the subnet where the endpoint needs to be created. | +| [`tags`](#parameter-privateendpointstags) | No | object | Optional. Tags to be applied on all resources/resource groups in this deployment. | + +### Parameter: `privateEndpoints.applicationSecurityGroupResourceIds` + +Optional. Application security groups in which the private endpoint IP configuration is included. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.customDnsConfigs` + +Optional. Custom DNS configurations. + +- Required: No +- Type: array + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`fqdn`](#parameter-privateendpointscustomdnsconfigsfqdn) | No | string | | +| [`ipAddresses`](#parameter-privateendpointscustomdnsconfigsipaddresses) | Yes | array | | + +### Parameter: `privateEndpoints.customDnsConfigs.fqdn` +- Required: No +- Type: string + +### Parameter: `privateEndpoints.customDnsConfigs.ipAddresses` +- Required: Yes +- Type: array + + +### Parameter: `privateEndpoints.customNetworkInterfaceName` + +Optional. The custom name of the network interface attached to the private endpoint. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.enableTelemetry` + +Optional. Enable/Disable usage telemetry for module. + +- Required: No +- Type: bool + +### Parameter: `privateEndpoints.ipConfigurations` + +Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints. + +- Required: No +- Type: array + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`groupId`](#parameter-privateendpointsipconfigurationsgroupid) | Yes | string | | +| [`memberName`](#parameter-privateendpointsipconfigurationsmembername) | Yes | string | | +| [`name`](#parameter-privateendpointsipconfigurationsname) | Yes | string | | +| [`privateIpAddress`](#parameter-privateendpointsipconfigurationsprivateipaddress) | Yes | string | | + +### Parameter: `privateEndpoints.ipConfigurations.groupId` +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.ipConfigurations.memberName` +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.ipConfigurations.name` +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.ipConfigurations.privateIpAddress` +- Required: Yes +- Type: string + + +### Parameter: `privateEndpoints.location` + +Optional. The location to deploy the private endpoint to. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.lock` + +Optional. Specify the type of lock. + +- Required: No +- Type: object + +### Parameter: `privateEndpoints.manualPrivateLinkServiceConnections` + +Optional. Manual PrivateLink Service Connections. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.name` + +Optional. The name of the private endpoint. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.privateDnsZoneGroupName` + +Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.privateDnsZoneResourceIds` + +Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.roleAssignments` + +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'. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.service` + +Optional. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob". + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.subnetResourceId` + +Required. Resource ID of the subnet where the endpoint needs to be created. + +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.tags` + +Optional. Tags to be applied on all resources/resource groups in this deployment. + +- Required: No +- Type: object ### Parameter: `publicNetworkAccess` diff --git a/modules/web/site/slot/main.bicep b/modules/web/site/slot/main.bicep index c0e56d8973..7b52d9bb53 100644 --- a/modules/web/site/slot/main.bicep +++ b/modules/web/site/slot/main.bicep @@ -70,7 +70,7 @@ param authSettingV2Configuration object = {} param lock lockType @description('Optional. Configuration details for private endpoints.') -param privateEndpoints array = [] +param privateEndpoints privateEndpointType @description('Optional. Tags of the resource.') param tags object = {} @@ -345,24 +345,27 @@ resource slot_roleAssignments 'Microsoft.Authorization/roleAssignments@2022-04-0 scope: slot }] -module slot_privateEndpoints '../../../network/private-endpoint/main.bicep' = [for (privateEndpoint, index) in privateEndpoints: { - name: '${uniqueString(deployment().name, location)}-Slot-${name}-PrivateEndpoint-${index}' +module slot_privateEndpoints '../../../network/private-endpoint/main.bicep' = [for (privateEndpoint, index) in (privateEndpoints ?? []): { + name: '${uniqueString(deployment().name, location)}-app-PrivateEndpoint-${index}' params: { groupIds: [ - '${privateEndpoint.service}-${name}' + privateEndpoint.?service ?? 'sites' ] - name: contains(privateEndpoint, 'name') ? privateEndpoint.name : 'pe-${last(split(slot.id, '/'))}-${privateEndpoint.service}-${index}' + name: privateEndpoint.?name ?? 'pep-${last(split(app.id, '/'))}-${privateEndpoint.?service ?? 'sites'}-${index}' serviceResourceId: app.id subnetResourceId: privateEndpoint.subnetResourceId - enableDefaultTelemetry: enableReferencedModulesTelemetry - location: contains(privateEndpoint, 'location') ? privateEndpoint.location : reference(split(privateEndpoint.subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location + enableDefaultTelemetry: privateEndpoint.?enableDefaultTelemetry ?? enableReferencedModulesTelemetry + location: privateEndpoint.?location ?? reference(split(privateEndpoint.subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location lock: privateEndpoint.?lock ?? lock - privateDnsZoneGroupName: contains(privateEndpoint, 'privateDnsZoneGroupName') ? privateEndpoint.privateDnsZoneGroupName : 'default' - privateDnsZoneResourceIds: contains(privateEndpoint, 'privateDnsZoneResourceIds') ? privateEndpoint.privateDnsZoneResourceIds : [] - roleAssignments: contains(privateEndpoint, 'roleAssignments') ? privateEndpoint.roleAssignments : [] - tags: contains(privateEndpoint, 'tags') ? privateEndpoint.tags : {} - manualPrivateLinkServiceConnections: contains(privateEndpoint, 'manualPrivateLinkServiceConnections') ? privateEndpoint.manualPrivateLinkServiceConnections : [] - customDnsConfigs: contains(privateEndpoint, 'customDnsConfigs') ? privateEndpoint.customDnsConfigs : [] + privateDnsZoneGroupName: privateEndpoint.?privateDnsZoneGroupName + privateDnsZoneResourceIds: privateEndpoint.?privateDnsZoneResourceIds + roleAssignments: privateEndpoint.?roleAssignments + tags: privateEndpoint.?tags ?? tags + manualPrivateLinkServiceConnections: privateEndpoint.?manualPrivateLinkServiceConnections + customDnsConfigs: privateEndpoint.?customDnsConfigs + ipConfigurations: privateEndpoint.?ipConfigurations + applicationSecurityGroupResourceIds: privateEndpoint.?applicationSecurityGroupResourceIds + customNetworkInterfaceName: privateEndpoint.?customNetworkInterfaceName } }] @@ -415,3 +418,58 @@ type roleAssignmentType = { @description('Optional. The Resource Id of the delegated managed identity resource.') delegatedManagedIdentityResourceId: string? }[]? + +type privateEndpointType = { + @description('Optional. The name of the private endpoint.') + name: string? + + @description('Optional. The location to deploy the private endpoint to.') + location: string? + + @description('Optional. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob".') + service: string? + + @description('Required. Resource ID of the subnet where the endpoint needs to be created.') + subnetResourceId: string + + @description('Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided.') + privateDnsZoneGroupName: string? + + @description('Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones.') + privateDnsZoneResourceIds: string[]? + + @description('Optional. Custom DNS configurations.') + customDnsConfigs: { + fqdn: string? + ipAddresses: string[] + }[]? + + @description('Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints.') + ipConfigurations: { + name: string + groupId: string + memberName: string + privateIpAddress: string + }[]? + + @description('Optional. Application security groups in which the private endpoint IP configuration is included.') + applicationSecurityGroupResourceIds: string[]? + + @description('Optional. The custom name of the network interface attached to the private endpoint.') + customNetworkInterfaceName: string? + + @description('Optional. Specify the type of lock.') + lock: lockType + + @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\'.') + roleAssignments: roleAssignmentType + + @description('Optional. Tags to be applied on all resources/resource groups in this deployment.') + tags: object? + + @description('Optional. Manual PrivateLink Service Connections.') + manualPrivateLinkServiceConnections: array? + + @description('Optional. Enable/Disable usage telemetry for module.') + enableTelemetry: bool? +}[]? diff --git a/modules/web/site/slot/main.json b/modules/web/site/slot/main.json index b9498b5fbe..3bfc8e59bb 100644 --- a/modules/web/site/slot/main.json +++ b/modules/web/site/slot/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.22.6.54827", - "templateHash": "11020134105665438870" + "templateHash": "17150701166857849727" }, "name": "Web/Function App Deployment Slots", "description": "This module deploys a Web or Function App Deployment Slot.", @@ -103,6 +103,155 @@ } }, "nullable": true + }, + "privateEndpointType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private endpoint." + } + }, + "location": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The location to deploy the private endpoint to." + } + }, + "service": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The service (sub-) type to deploy the private endpoint for. For example \"vault\" or \"blob\"." + } + }, + "subnetResourceId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the subnet where the endpoint needs to be created." + } + }, + "privateDnsZoneGroupName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided." + } + }, + "privateDnsZoneResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones." + } + }, + "customDnsConfigs": { + "type": "array", + "items": { + "type": "object", + "properties": { + "fqdn": { + "type": "string", + "nullable": true + }, + "ipAddresses": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. Custom DNS configurations." + } + }, + "ipConfigurations": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "groupId": { + "type": "string" + }, + "memberName": { + "type": "string" + }, + "privateIpAddress": { + "type": "string" + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints." + } + }, + "applicationSecurityGroupResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. Application security groups in which the private endpoint IP configuration is included." + } + }, + "customNetworkInterfaceName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The custom name of the network interface attached to the private endpoint." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "metadata": { + "description": "Optional. Specify the type of lock." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "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'." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags to be applied on all resources/resource groups in this deployment." + } + }, + "manualPrivateLinkServiceConnections": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. Manual PrivateLink Service Connections." + } + }, + "enableTelemetry": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + } + } + }, + "nullable": true } }, "parameters": { @@ -250,8 +399,7 @@ } }, "privateEndpoints": { - "type": "array", - "defaultValue": [], + "$ref": "#/definitions/privateEndpointType", "metadata": { "description": "Optional. Configuration details for private endpoints." } @@ -1058,11 +1206,11 @@ "slot_privateEndpoints": { "copy": { "name": "slot_privateEndpoints", - "count": "[length(parameters('privateEndpoints'))]" + "count": "[length(coalesce(parameters('privateEndpoints'), createArray()))]" }, "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", - "name": "[format('{0}-Slot-{1}-PrivateEndpoint-{2}', uniqueString(deployment().name, parameters('location')), parameters('name'), copyIndex())]", + "name": "[format('{0}-app-PrivateEndpoint-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", "properties": { "expressionEvaluationOptions": { "scope": "inner" @@ -1071,29 +1219,54 @@ "parameters": { "groupIds": { "value": [ - "[format('{0}-{1}', parameters('privateEndpoints')[copyIndex()].service, parameters('name'))]" + "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'sites')]" ] }, - "name": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'name'), createObject('value', parameters('privateEndpoints')[copyIndex()].name), createObject('value', format('pe-{0}-{1}-{2}', last(split(resourceId('Microsoft.Web/sites/slots', parameters('appName'), parameters('name')), '/')), parameters('privateEndpoints')[copyIndex()].service, copyIndex())))]", + "name": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'name'), format('pep-{0}-{1}-{2}', last(split(resourceId('Microsoft.Web/sites', parameters('appName')), '/')), coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'sites'), copyIndex()))]" + }, "serviceResourceId": { "value": "[resourceId('Microsoft.Web/sites', parameters('appName'))]" }, "subnetResourceId": { - "value": "[parameters('privateEndpoints')[copyIndex()].subnetResourceId]" + "value": "[coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId]" }, "enableDefaultTelemetry": { - "value": "[variables('enableReferencedModulesTelemetry')]" + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'enableDefaultTelemetry'), variables('enableReferencedModulesTelemetry'))]" + }, + "location": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'location'), reference(split(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location)]" }, - "location": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'location'), createObject('value', parameters('privateEndpoints')[copyIndex()].location), createObject('value', reference(split(parameters('privateEndpoints')[copyIndex()].subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location))]", "lock": { - "value": "[coalesce(tryGet(parameters('privateEndpoints')[copyIndex()], 'lock'), parameters('lock'))]" + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'lock'), parameters('lock'))]" + }, + "privateDnsZoneGroupName": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneGroupName')]" + }, + "privateDnsZoneResourceIds": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneResourceIds')]" + }, + "roleAssignments": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'roleAssignments')]" + }, + "tags": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'tags'), parameters('tags'))]" + }, + "manualPrivateLinkServiceConnections": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'manualPrivateLinkServiceConnections')]" + }, + "customDnsConfigs": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customDnsConfigs')]" }, - "privateDnsZoneGroupName": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'privateDnsZoneGroupName'), createObject('value', parameters('privateEndpoints')[copyIndex()].privateDnsZoneGroupName), createObject('value', 'default'))]", - "privateDnsZoneResourceIds": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'privateDnsZoneResourceIds'), createObject('value', parameters('privateEndpoints')[copyIndex()].privateDnsZoneResourceIds), createObject('value', createArray()))]", - "roleAssignments": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'roleAssignments'), createObject('value', parameters('privateEndpoints')[copyIndex()].roleAssignments), createObject('value', createArray()))]", - "tags": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'tags'), createObject('value', parameters('privateEndpoints')[copyIndex()].tags), createObject('value', createObject()))]", - "manualPrivateLinkServiceConnections": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'manualPrivateLinkServiceConnections'), createObject('value', parameters('privateEndpoints')[copyIndex()].manualPrivateLinkServiceConnections), createObject('value', createArray()))]", - "customDnsConfigs": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'customDnsConfigs'), createObject('value', parameters('privateEndpoints')[copyIndex()].customDnsConfigs), createObject('value', createArray()))]" + "ipConfigurations": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'ipConfigurations')]" + }, + "applicationSecurityGroupResourceIds": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'applicationSecurityGroupResourceIds')]" + }, + "customNetworkInterfaceName": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customNetworkInterfaceName')]" + } }, "template": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", @@ -1578,8 +1751,7 @@ } }, "dependsOn": [ - "app", - "slot" + "app" ] } }, diff --git a/modules/web/static-site/.test/common/main.test.bicep b/modules/web/static-site/.test/common/main.test.bicep index df0aa09ac0..cd2de2ac13 100644 --- a/modules/web/static-site/.test/common/main.test.bicep +++ b/modules/web/static-site/.test/common/main.test.bicep @@ -63,7 +63,6 @@ module testDeployment '../../main.bicep' = { } privateEndpoints: [ { - service: 'staticSites' subnetResourceId: nestedDependencies.outputs.subnetResourceId privateDnsZoneResourceIds: [ nestedDependencies.outputs.privateDNSZoneResourceId diff --git a/modules/web/static-site/README.md b/modules/web/static-site/README.md index e52301ee36..0f632c9a57 100644 --- a/modules/web/static-site/README.md +++ b/modules/web/static-site/README.md @@ -73,7 +73,6 @@ module staticSite 'br:bicep/modules/web.static-site:1.0.0' = { privateDnsZoneResourceIds: [ '' ] - service: 'staticSites' subnetResourceId: '' tags: { Environment: 'Non-Prod' @@ -159,7 +158,6 @@ module staticSite 'br:bicep/modules/web.static-site:1.0.0' = { "privateDnsZoneResourceIds": [ "" ], - "service": "staticSites", "subnetResourceId": "", "tags": { "Environment": "Non-Prod", @@ -399,7 +397,168 @@ Name of the static site. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible. Note, requires the 'sku' to be 'Standard'. - Required: No - Type: array -- Default: `[]` + + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`applicationSecurityGroupResourceIds`](#parameter-privateendpointsapplicationsecuritygroupresourceids) | No | array | Optional. Application security groups in which the private endpoint IP configuration is included. | +| [`customDnsConfigs`](#parameter-privateendpointscustomdnsconfigs) | No | array | Optional. Custom DNS configurations. | +| [`customNetworkInterfaceName`](#parameter-privateendpointscustomnetworkinterfacename) | No | string | Optional. The custom name of the network interface attached to the private endpoint. | +| [`enableTelemetry`](#parameter-privateendpointsenabletelemetry) | No | bool | Optional. Enable/Disable usage telemetry for module. | +| [`ipConfigurations`](#parameter-privateendpointsipconfigurations) | No | array | Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints. | +| [`location`](#parameter-privateendpointslocation) | No | string | Optional. The location to deploy the private endpoint to. | +| [`lock`](#parameter-privateendpointslock) | No | object | Optional. Specify the type of lock. | +| [`manualPrivateLinkServiceConnections`](#parameter-privateendpointsmanualprivatelinkserviceconnections) | No | array | Optional. Manual PrivateLink Service Connections. | +| [`name`](#parameter-privateendpointsname) | No | string | Optional. The name of the private endpoint. | +| [`privateDnsZoneGroupName`](#parameter-privateendpointsprivatednszonegroupname) | No | string | Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided. | +| [`privateDnsZoneResourceIds`](#parameter-privateendpointsprivatednszoneresourceids) | No | array | Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones. | +| [`roleAssignments`](#parameter-privateendpointsroleassignments) | No | array | 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'. | +| [`service`](#parameter-privateendpointsservice) | No | string | Optional. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob". | +| [`subnetResourceId`](#parameter-privateendpointssubnetresourceid) | Yes | string | Required. Resource ID of the subnet where the endpoint needs to be created. | +| [`tags`](#parameter-privateendpointstags) | No | object | Optional. Tags to be applied on all resources/resource groups in this deployment. | + +### Parameter: `privateEndpoints.applicationSecurityGroupResourceIds` + +Optional. Application security groups in which the private endpoint IP configuration is included. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.customDnsConfigs` + +Optional. Custom DNS configurations. + +- Required: No +- Type: array + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`fqdn`](#parameter-privateendpointscustomdnsconfigsfqdn) | No | string | | +| [`ipAddresses`](#parameter-privateendpointscustomdnsconfigsipaddresses) | Yes | array | | + +### Parameter: `privateEndpoints.customDnsConfigs.fqdn` +- Required: No +- Type: string + +### Parameter: `privateEndpoints.customDnsConfigs.ipAddresses` +- Required: Yes +- Type: array + + +### Parameter: `privateEndpoints.customNetworkInterfaceName` + +Optional. The custom name of the network interface attached to the private endpoint. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.enableTelemetry` + +Optional. Enable/Disable usage telemetry for module. + +- Required: No +- Type: bool + +### Parameter: `privateEndpoints.ipConfigurations` + +Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints. + +- Required: No +- Type: array + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`groupId`](#parameter-privateendpointsipconfigurationsgroupid) | Yes | string | | +| [`memberName`](#parameter-privateendpointsipconfigurationsmembername) | Yes | string | | +| [`name`](#parameter-privateendpointsipconfigurationsname) | Yes | string | | +| [`privateIpAddress`](#parameter-privateendpointsipconfigurationsprivateipaddress) | Yes | string | | + +### Parameter: `privateEndpoints.ipConfigurations.groupId` +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.ipConfigurations.memberName` +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.ipConfigurations.name` +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.ipConfigurations.privateIpAddress` +- Required: Yes +- Type: string + + +### Parameter: `privateEndpoints.location` + +Optional. The location to deploy the private endpoint to. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.lock` + +Optional. Specify the type of lock. + +- Required: No +- Type: object + +### Parameter: `privateEndpoints.manualPrivateLinkServiceConnections` + +Optional. Manual PrivateLink Service Connections. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.name` + +Optional. The name of the private endpoint. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.privateDnsZoneGroupName` + +Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.privateDnsZoneResourceIds` + +Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.roleAssignments` + +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'. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.service` + +Optional. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob". + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.subnetResourceId` + +Required. Resource ID of the subnet where the endpoint needs to be created. + +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.tags` + +Optional. Tags to be applied on all resources/resource groups in this deployment. + +- Required: No +- Type: object ### Parameter: `provider` diff --git a/modules/web/static-site/main.bicep b/modules/web/static-site/main.bicep index 757e3ae5b6..efe8df8ec6 100644 --- a/modules/web/static-site/main.bicep +++ b/modules/web/static-site/main.bicep @@ -65,7 +65,7 @@ param userAssignedIdentities object = {} param lock lockType @description('Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible. Note, requires the \'sku\' to be \'Standard\'.') -param privateEndpoints array = [] +param privateEndpoints privateEndpointType @description('Optional. Tags of the resource.') param tags object = {} @@ -204,27 +204,27 @@ resource staticSite_roleAssignments 'Microsoft.Authorization/roleAssignments@202 scope: staticSite }] -module staticSite_privateEndpoints '../../network/private-endpoint/main.bicep' = [for (privateEndpoint, index) in privateEndpoints: { - name: '${uniqueString(deployment().name, location)}-StaticSite-PrivateEndpoint-${index}' +module staticSite_privateEndpoints '../../network/private-endpoint/main.bicep' = [for (privateEndpoint, index) in (privateEndpoints ?? []): { + name: '${uniqueString(deployment().name, location)}-staticSite-PrivateEndpoint-${index}' params: { groupIds: [ - privateEndpoint.service + privateEndpoint.?service ?? 'staticSites' ] - name: contains(privateEndpoint, 'name') ? privateEndpoint.name : 'pe-${last(split(staticSite.id, '/'))}-${privateEndpoint.service}-${index}' + name: privateEndpoint.?name ?? 'pep-${last(split(staticSite.id, '/'))}-${privateEndpoint.?service ?? 'staticSites'}-${index}' serviceResourceId: staticSite.id subnetResourceId: privateEndpoint.subnetResourceId - enableDefaultTelemetry: enableReferencedModulesTelemetry - location: contains(privateEndpoint, 'location') ? privateEndpoint.location : reference(split(privateEndpoint.subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location + enableDefaultTelemetry: privateEndpoint.?enableDefaultTelemetry ?? enableReferencedModulesTelemetry + location: privateEndpoint.?location ?? reference(split(privateEndpoint.subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location lock: privateEndpoint.?lock ?? lock - privateDnsZoneGroupName: contains(privateEndpoint, 'privateDnsZoneGroupName') ? privateEndpoint.privateDnsZoneGroupName : 'default' - privateDnsZoneResourceIds: contains(privateEndpoint, 'privateDnsZoneResourceIds') ? privateEndpoint.privateDnsZoneResourceIds : [] - 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 : [] - applicationSecurityGroupResourceIds: contains(privateEndpoint, 'applicationSecurityGroupResourceIds') ? privateEndpoint.applicationSecurityGroupResourceIds : [] - customNetworkInterfaceName: contains(privateEndpoint, 'customNetworkInterfaceName') ? privateEndpoint.customNetworkInterfaceName : '' + privateDnsZoneGroupName: privateEndpoint.?privateDnsZoneGroupName + privateDnsZoneResourceIds: privateEndpoint.?privateDnsZoneResourceIds + roleAssignments: privateEndpoint.?roleAssignments + tags: privateEndpoint.?tags ?? tags + manualPrivateLinkServiceConnections: privateEndpoint.?manualPrivateLinkServiceConnections + customDnsConfigs: privateEndpoint.?customDnsConfigs + ipConfigurations: privateEndpoint.?ipConfigurations + applicationSecurityGroupResourceIds: privateEndpoint.?applicationSecurityGroupResourceIds + customNetworkInterfaceName: privateEndpoint.?customNetworkInterfaceName } }] @@ -280,3 +280,58 @@ type roleAssignmentType = { @description('Optional. The Resource Id of the delegated managed identity resource.') delegatedManagedIdentityResourceId: string? }[]? + +type privateEndpointType = { + @description('Optional. The name of the private endpoint.') + name: string? + + @description('Optional. The location to deploy the private endpoint to.') + location: string? + + @description('Optional. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob".') + service: string? + + @description('Required. Resource ID of the subnet where the endpoint needs to be created.') + subnetResourceId: string + + @description('Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided.') + privateDnsZoneGroupName: string? + + @description('Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones.') + privateDnsZoneResourceIds: string[]? + + @description('Optional. Custom DNS configurations.') + customDnsConfigs: { + fqdn: string? + ipAddresses: string[] + }[]? + + @description('Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints.') + ipConfigurations: { + name: string + groupId: string + memberName: string + privateIpAddress: string + }[]? + + @description('Optional. Application security groups in which the private endpoint IP configuration is included.') + applicationSecurityGroupResourceIds: string[]? + + @description('Optional. The custom name of the network interface attached to the private endpoint.') + customNetworkInterfaceName: string? + + @description('Optional. Specify the type of lock.') + lock: lockType + + @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\'.') + roleAssignments: roleAssignmentType + + @description('Optional. Tags to be applied on all resources/resource groups in this deployment.') + tags: object? + + @description('Optional. Manual PrivateLink Service Connections.') + manualPrivateLinkServiceConnections: array? + + @description('Optional. Enable/Disable usage telemetry for module.') + enableTelemetry: bool? +}[]? diff --git a/modules/web/static-site/main.json b/modules/web/static-site/main.json index 3d50d77660..b992f8c721 100644 --- a/modules/web/static-site/main.json +++ b/modules/web/static-site/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.22.6.54827", - "templateHash": "10437554075248672747" + "templateHash": "631543863258215268" }, "name": "Static Web Apps", "description": "This module deploys a Static Web App.", @@ -103,6 +103,155 @@ } }, "nullable": true + }, + "privateEndpointType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private endpoint." + } + }, + "location": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The location to deploy the private endpoint to." + } + }, + "service": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The service (sub-) type to deploy the private endpoint for. For example \"vault\" or \"blob\"." + } + }, + "subnetResourceId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the subnet where the endpoint needs to be created." + } + }, + "privateDnsZoneGroupName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided." + } + }, + "privateDnsZoneResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones." + } + }, + "customDnsConfigs": { + "type": "array", + "items": { + "type": "object", + "properties": { + "fqdn": { + "type": "string", + "nullable": true + }, + "ipAddresses": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. Custom DNS configurations." + } + }, + "ipConfigurations": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "groupId": { + "type": "string" + }, + "memberName": { + "type": "string" + }, + "privateIpAddress": { + "type": "string" + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints." + } + }, + "applicationSecurityGroupResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. Application security groups in which the private endpoint IP configuration is included." + } + }, + "customNetworkInterfaceName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The custom name of the network interface attached to the private endpoint." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "metadata": { + "description": "Optional. Specify the type of lock." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "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'." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags to be applied on all resources/resource groups in this deployment." + } + }, + "manualPrivateLinkServiceConnections": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. Manual PrivateLink Service Connections." + } + }, + "enableTelemetry": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + } + } + }, + "nullable": true } }, "parameters": { @@ -226,8 +375,7 @@ } }, "privateEndpoints": { - "type": "array", - "defaultValue": [], + "$ref": "#/definitions/privateEndpointType", "metadata": { "description": "Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible. Note, requires the 'sku' to be 'Standard'." } @@ -881,11 +1029,11 @@ "staticSite_privateEndpoints": { "copy": { "name": "staticSite_privateEndpoints", - "count": "[length(parameters('privateEndpoints'))]" + "count": "[length(coalesce(parameters('privateEndpoints'), createArray()))]" }, "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", - "name": "[format('{0}-StaticSite-PrivateEndpoint-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "name": "[format('{0}-staticSite-PrivateEndpoint-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", "properties": { "expressionEvaluationOptions": { "scope": "inner" @@ -894,32 +1042,54 @@ "parameters": { "groupIds": { "value": [ - "[parameters('privateEndpoints')[copyIndex()].service]" + "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'staticSites')]" ] }, - "name": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'name'), createObject('value', parameters('privateEndpoints')[copyIndex()].name), createObject('value', format('pe-{0}-{1}-{2}', last(split(resourceId('Microsoft.Web/staticSites', parameters('name')), '/')), parameters('privateEndpoints')[copyIndex()].service, copyIndex())))]", + "name": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'name'), format('pep-{0}-{1}-{2}', last(split(resourceId('Microsoft.Web/staticSites', parameters('name')), '/')), coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'staticSites'), copyIndex()))]" + }, "serviceResourceId": { "value": "[resourceId('Microsoft.Web/staticSites', parameters('name'))]" }, "subnetResourceId": { - "value": "[parameters('privateEndpoints')[copyIndex()].subnetResourceId]" + "value": "[coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId]" }, "enableDefaultTelemetry": { - "value": "[variables('enableReferencedModulesTelemetry')]" + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'enableDefaultTelemetry'), variables('enableReferencedModulesTelemetry'))]" + }, + "location": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'location'), reference(split(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location)]" }, - "location": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'location'), createObject('value', parameters('privateEndpoints')[copyIndex()].location), createObject('value', reference(split(parameters('privateEndpoints')[copyIndex()].subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location))]", "lock": { - "value": "[coalesce(tryGet(parameters('privateEndpoints')[copyIndex()], 'lock'), parameters('lock'))]" + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'lock'), parameters('lock'))]" + }, + "privateDnsZoneGroupName": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneGroupName')]" + }, + "privateDnsZoneResourceIds": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneResourceIds')]" + }, + "roleAssignments": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'roleAssignments')]" }, - "privateDnsZoneGroupName": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'privateDnsZoneGroupName'), createObject('value', parameters('privateEndpoints')[copyIndex()].privateDnsZoneGroupName), createObject('value', 'default'))]", - "privateDnsZoneResourceIds": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'privateDnsZoneResourceIds'), createObject('value', parameters('privateEndpoints')[copyIndex()].privateDnsZoneResourceIds), createObject('value', createArray()))]", - "roleAssignments": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'roleAssignments'), createObject('value', parameters('privateEndpoints')[copyIndex()].roleAssignments), createObject('value', createArray()))]", - "tags": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'tags'), createObject('value', parameters('privateEndpoints')[copyIndex()].tags), createObject('value', createObject()))]", - "manualPrivateLinkServiceConnections": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'manualPrivateLinkServiceConnections'), createObject('value', parameters('privateEndpoints')[copyIndex()].manualPrivateLinkServiceConnections), createObject('value', createArray()))]", - "customDnsConfigs": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'customDnsConfigs'), createObject('value', parameters('privateEndpoints')[copyIndex()].customDnsConfigs), createObject('value', createArray()))]", - "ipConfigurations": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'ipConfigurations'), createObject('value', parameters('privateEndpoints')[copyIndex()].ipConfigurations), createObject('value', createArray()))]", - "applicationSecurityGroupResourceIds": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'applicationSecurityGroupResourceIds'), createObject('value', parameters('privateEndpoints')[copyIndex()].applicationSecurityGroupResourceIds), createObject('value', createArray()))]", - "customNetworkInterfaceName": "[if(contains(parameters('privateEndpoints')[copyIndex()], 'customNetworkInterfaceName'), createObject('value', parameters('privateEndpoints')[copyIndex()].customNetworkInterfaceName), createObject('value', ''))]" + "tags": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'tags'), parameters('tags'))]" + }, + "manualPrivateLinkServiceConnections": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'manualPrivateLinkServiceConnections')]" + }, + "customDnsConfigs": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customDnsConfigs')]" + }, + "ipConfigurations": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'ipConfigurations')]" + }, + "applicationSecurityGroupResourceIds": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'applicationSecurityGroupResourceIds')]" + }, + "customNetworkInterfaceName": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customNetworkInterfaceName')]" + } }, "template": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",