Don Chambers
Don Chambers

Reputation: 4289

Language service - disable public network access then add private endpoint

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: []
  }
}

Upvotes: 1

Views: 397

Answers (2)

Thomas
Thomas

Reputation: 29726

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

Upvotes: 0

Thomas
Thomas

Reputation: 29726

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
        }
      }
    ]
  }
}

Upvotes: 0

Related Questions