skip to Main Content

I am deploying a langauge service using a bicep file. I set public network access to disabled. In the next step, I setup a private endpoint.

I am getting this error:

Failed to disable Public Access for Azure Search. Additional steps are
required to setup a private link to your Azure Cognitive Search
service.

I tried deploying without setting public network access to disabled, and it worked. This creates the language service and the private endpoint. Then I changed public network access to disabled and depolyed again, which worked.

I think it will not allow me to disable public network access without the private endpoint in place.

How do I get around this?
I don’t see a way to disable public network access without copying all the resource settings in another block. That is messy.

Is there a way to update a single setting? I don’t want to change anything else by omitting it.

Update: I don’t get this problem with I remove the association with the service service.
If I remove this section, it works

apiProperties: {
      qnaAzureSearchEndpointId: search.id
      qnaAzureSearchEndpointKey: search.listAdminKeys().primaryKey
    }

Here is the bicep:

param languageServiceName string = 'lg-usas-loginbot-dev-cus-001'
param searchServiceName string = 'ais-usas-loginbot-dev-cus-001'
param location string = 'CentralUS'
param privateEndpointVnetResourceGroup string = 'rg-hrs-usas-np-inf-01'
param privateEndpointVnet string = 'vnet-hrs-usas-dev-cus'
param privateEndpointSubnet string = 'sn-chatbot-dev-002'

resource search 'Microsoft.Search/searchServices@2024-06-01-preview' existing = {
  name: searchServiceName
}


resource language 'Microsoft.CognitiveServices/accounts@2024-06-01-preview' = {
  name: languageServiceName
  location: location
  sku: {
    name: 'S'
  }
  kind: 'TextAnalytics'
  properties: {
    apiProperties: {
      qnaAzureSearchEndpointId: search.id
      qnaAzureSearchEndpointKey: search.listAdminKeys().primaryKey
    }
    customSubDomainName: languageServiceName
    networkAcls: {
      defaultAction: 'Allow'
      virtualNetworkRules: []
      ipRules: []
    }
    publicNetworkAccess: 'Disabled'
  }
  identity: {
    type: 'SystemAssigned'
  }
}


resource existingPESubnet 'Microsoft.Network/virtualNetworks/subnets@2022-05-01' existing = {
  name: '${privateEndpointVnet}/${privateEndpointSubnet}'
  scope: resourceGroup(privateEndpointVnetResourceGroup)
}

resource languagePE 'Microsoft.Network/privateEndpoints@2022-05-01' = {
  name: 'pe-${languageServiceName}-as'
  location: location
  properties: {
    privateLinkServiceConnections: [
      {
        name: 'pe-${languageServiceName}-as'
        properties: {
          privateLinkServiceId: language.id
          groupIds: [
            'account'
          ]
          privateLinkServiceConnectionState: {
            status: 'Approved'
            description: 'Auto-approved'
            actionsRequired: 'None'
          }
        }
      }
    ]
    manualPrivateLinkServiceConnections: []
    customNetworkInterfaceName: 'nic-${languageServiceName}-as'
    subnet: {
      id: existingPESubnet.id
    }
    ipConfigurations: []
  }
}

2

Answers


  1. Just tried with a language service and no error:

    • Create VNET
    • Create DNS zone and VNET link
    • Create language service
    • Create private endpoint and private link to dns zone
    param location string = resourceGroup().location
    param vnetName string = 'vnet-thomas-test-001'
    param languageServiceName string = 'language-ai-thomas-test-001'
    
    var subnetName = 'private-endpoints'
    
    // Create basic vnet
    resource vnet 'Microsoft.Network/virtualNetworks@2024-01-01' = {
      name: vnetName
      location: location
      properties: {
        addressSpace: {
          addressPrefixes: [
            '10.0.0.0/16'
          ]
        }
        subnets: [
          {
            name: 'default'
            properties: {
              addressPrefix: '10.0.0.0/24'
            }
          }
          {
            name: subnetName
            properties: {
              addressPrefix: '10.0.1.0/24'
            }
          }
        ]
      }
    }
    
    // Create a private dns zone
    resource privateDnsZone 'Microsoft.Network/privateDnsZones@2024-06-01' = {
      name: 'privatelink.cognitiveservices.azure.com'
      location: 'global'
    }
    
    // Create a link to the vnet
    resource privateDnsZoneVnetLink 'Microsoft.Network/privateDnsZones/virtualNetworkLinks@2024-06-01' = {
      name: vnet.name
      parent: privateDnsZone
      location: 'global'
      properties: {
        registrationEnabled: false
        virtualNetwork:{
          id: vnet.id
        }
      }
    }
    
    // Create basic language service
    resource languageService 'Microsoft.CognitiveServices/accounts@2024-06-01-preview' = {
      name: languageServiceName
      location: location  
      kind: 'TextAnalytics'
      identity: {
        type: 'SystemAssigned'
      }
      sku: {
        name: 'S'
      }
      properties: {
        customSubDomainName: toLower(languageServiceName)    
        publicNetworkAccess: 'disabled'    
      }
    }
    
    // Create the private endpoint
    resource privateEndpoint 'Microsoft.Network/privateEndpoints@2023-05-01' = {
      name: 'pe-${languageService.name}'
      location: location
      properties: {
        subnet: {
          id: resourceId('Microsoft.Network/virtualNetworks/subnets', vnet.name, subnetName)
        }
        customNetworkInterfaceName: 'pe-${languageService.name}-nic'
        privateLinkServiceConnections: [
          {
            name: 'plsc-${languageService.name}'
            properties: {
              privateLinkServiceId: languageService.id
              groupIds: [ 'account' ]
            }
          }
        ]
      }
    }
    
    // Associate private link to private dns zone
    resource privateDnsZoneGroup 'Microsoft.Network/privateEndpoints/privateDnsZoneGroups@2023-05-01' = {
      name: 'default'
      parent: privateEndpoint
      properties: {
        privateDnsZoneConfigs: [
          {
            name: replace(privateDnsZone.name, '.', '-')
            properties: {
              privateDnsZoneId: privateDnsZone.id
            }
          }
        ]
      }
    }
    
    Login or Signup to reply.
  2. Posting a new answer as I think the other one can still be relevant for others but not answering your main issue.

    So I found this piece of documentation: Network isolation and private endpoints:

    • Create a search service with public access
    • Create a language service with public access and associate the search service tp it
    • Grant the language service identity contributor role over the search service
    • Disable public access on the search service
    • Disable public access on the language service: This will create a private endpoint between the two services. This created private endpoint does not live in your subscription, it is on the microsoft network.

    I feel that would be best addressed with az cli because of the multiple steps required but managed to get an almost bicep version.
    Before the deployment starts, I’m checking if the resources exist to make it idempotent => this also could be done using deploymentScripts but I feel it is over complicated to the purpose of your question.

    To piece everything together, I had to create few modules.

    • search-ai.bicep
    param location string = resourceGroup().location
    param searchServiceName string
    param publicNetworkAccess string
    
    output name string = searchService.name
    
    // Create search service
    resource searchService 'Microsoft.Search/searchServices@2024-06-01-preview' = {
      name: searchServiceName
      sku: {
        name: 'standard'
      }
      location: location
      properties: {
        replicaCount: 1
        partitionCount: 1
        hostingMode: 'default'
        disableLocalAuth: false
        semanticSearch: 'disabled'
        publicNetworkAccess: publicNetworkAccess
      } 
    }
    
    • search-ai-role-assignment.bicep
    param searchSearviceName string
    param roleId string
    param principalId string
    param principalType string = 'ServicePrincipal'
    
    resource searchService 'Microsoft.Search/searchServices@2024-06-01-preview' existing = {
      name: searchSearviceName
    }
    
    resource roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
      name: guid(searchService.id, roleId, principalId)
      scope: searchService
      properties: {
        roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleId)
        principalId: principalId
        principalType: principalType
      }
    }
    
    • language-ai.bicep
    param location string = resourceGroup().location
    param languageServiceName string
    param searchServiceName string
    param publicNetworkAccess string = 'Enabled'
    
    output name string = languageService.name
    output principalId string = languageService.identity.principalId
    
    // Get a reference to the search service
    resource searchService 'Microsoft.Search/searchServices@2024-06-01-preview' existing = {
      name: searchServiceName  
    }
    
    // Create language service
    resource languageService 'Microsoft.CognitiveServices/accounts@2024-06-01-preview' = {
      name: languageServiceName
      location: location  
      kind: 'TextAnalytics'
      identity: {
        type: 'SystemAssigned'
      }
      sku: {
        name: 'S'
      }
      properties: {
        publicNetworkAccess: publicNetworkAccess
        customSubDomainName: toLower(languageServiceName)    
        apiProperties: {
          qnaAzureSearchEndpointId: searchService.id
          qnaAzureSearchEndpointKey: searchService.listAdminKeys().primaryKey
        }
      }
    }
    
    • main.bicep
    param searchServiceName string
    param languageServiceName string
    param searchServiceExists bool
    param languageServiceExists bool
    
    // Create search service with public netwok enable if the service and language service do not exist
    var searchService1PublicNetworkAccess = searchServiceExists && languageServiceExists ? 'disabled' : 'enabled'
    module searchService1 'search-ai.bicep' = {
      name: '${searchServiceName}-1'
      params: {
        searchServiceName: searchServiceName
        publicNetworkAccess: searchService1PublicNetworkAccess
      }
    }
    
    // Create language service with public netwok enable if the service do not exist
    var languageService1PublicNetworkAccess = languageServiceExists ? 'Disabled' : 'Enabled'
    module languageService1 'language-ai.bicep' = {
      name: '${languageServiceName}-1'
      params: {
        languageServiceName: languageServiceName
        searchServiceName: searchService1.outputs.name
        publicNetworkAccess: languageService1PublicNetworkAccess
      }
    }
    
    // Grant contributor role to the language service identity ove the search service
    // => This is required for the creation of the internal private endpoint between the two components
    module roleAssignment 'search-ai-role-assignment.bicep' = {
      name: 'search-language-rbac-contributor'
      params: {
        principalId: languageService1.outputs.principalId
        roleId: 'b24988ac-6180-42a0-ab88-20f7382dd24c' // Contributor
        searchSearviceName: searchService1.outputs.name
      }
    }
    
    // Disable public network on the search service if we just created the service
    module searchService2 'search-ai.bicep' = if (!searchServiceExists) {
      name: '${searchServiceName}-2'
      params: {
        searchServiceName: searchServiceName
        publicNetworkAccess: 'disabled'
      }
      // Explicit dependency
      dependsOn: [languageService1]
    }
    
    // Disable public network on the language service if we just created the service
    module languageService2 'language-ai.bicep' = if (!languageServiceExists) {
      name: '${languageServiceName}-2'
      params: {
        languageServiceName: languageServiceName
        searchServiceName: searchService2.outputs.name
        publicNetworkAccess: 'Disabled'
      }
      // Explicit dependency
      dependsOn: [roleAssignment, searchService2]
    }
    

    Then I can invoke the bicep script like that (using powershell):

    $resourceGroupName = "<resource group name>"
    $searchServiceName = "<search service name>"
    $languageServiceName = "<language service name>"
    $searchServiceExists = (az resource list --name "$searchServiceName" --query '[].[id]' | ConvertFrom-Json).Length -gt 0
    $languageServiceExists = (az resource list --name "$languageServiceName" --query '[].[id]' | ConvertFrom-Json).Length -gt 0
    
    az deployment group create `
      --resource-group "$resourceGroupName" `
      --template-file "./main.bicep" `
      --parameters `
      searchServiceName="$searchServiceName" `
      languageServiceName="$languageServiceName" `
      searchServiceExists=$searchServiceExists `
      languageServiceExists=$languageServiceExists
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search