Ejecución del script de implementación de Bicep de forma privada a través de un punto de conexión privado

Con la versión 2023-08-01 de la API de recursos Microsoft.Resources/deploymentScripts, puede ejecutar scripts de implementación de manera privada en una instancia de Azure Container Instances (ACI).

Configuración del entorno

En esta configuración, la ACI creada por el script de implementación se ejecuta dentro de una red virtual y obtiene una dirección IP privada. A continuación, establece una conexión a una cuenta de almacenamiento nueva o preexistente a través de un punto de conexión privado. La propiedad containerSettings/subnetIds especifica la ACI que se debe implementar en una subred de la red virtual.

Captura de pantalla de la arquitectura de alto nivel que muestra cómo se conecta la infraestructura para ejecutar scripts de implementación de forma privada.

Para ejecutar scripts de implementación de forma privada, necesita la siguiente infraestructura como se muestra en el diagrama de arquitectura:

  • Cree una red virtual con dos subredes:
    • Una subred para el punto de conexión privado.
    • Una subred para la ACI, esta subred necesita una delegación Microsoft.ContainerInstance/containerGroups.
  • Cree una cuenta de almacenamiento sin acceso a la red pública.
  • Cree un punto de conexión privado dentro de la red virtual configurada con el subrecurso file en la cuenta de almacenamiento.
  • Cree una zona DNS privada privatelink.file.core.windows.net y registre la dirección IP del punto de conexión privado como un registro A. Vincule la zona DNS privada a la red virtual creada.
  • Cree una identidad administrada asignada por el usuario con permisos Storage File Data Privileged Contributor en la cuenta de almacenamiento y especifíquela en la propiedad identity en el recurso de script de implementación. Para asignar la identidad, consulte Identidad.
  • El recurso ACI se crea automáticamente mediante el recurso de script de implementación.

Importante

Si asignas una identidad gestionada con permisos que superan los requisitos operativos del script, el autor del script es responsable de asegurar que la cuenta de almacenamiento esté protegida contra accesos no deseados. Siempre que sea posible, sigue el principio de menor privilegio.

El siguiente archivo de Bicep configura la infraestructura necesaria para ejecutar un script de implementación de forma privada:

@maxLength(10) // Required maximum length, because the storage account has a maximum of 26 characters
param namePrefix string
param location string = resourceGroup().location
param userAssignedIdentityName string = '${namePrefix}Identity'
param storageAccountName string = '${namePrefix}stg${uniqueString(resourceGroup().id)}'
param vnetName string = '${namePrefix}Vnet'
param deploymentScriptName string = '${namePrefix}ds'

var roleNameStorageFileDataPrivilegedContributor = '69566ab7-960f-475b-8e7c-b3118f30c6bd'
var vnetAddressPrefix = '192.168.4.0/23'
var subnetEndpointAddressPrefix = '192.168.4.0/24'
var subnetACIAddressPrefix = '192.168.5.0/24'

resource managedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2025-01-31-preview' = {
  name: userAssignedIdentityName
  location: location
}

resource storageAccount 'Microsoft.Storage/storageAccounts@2025-06-01' = {
  name: storageAccountName
  kind: 'StorageV2'
  location: location
  sku: {
    name: 'Standard_LRS'
  }
  properties: {
    publicNetworkAccess: 'Disabled'
    networkAcls: {
      defaultAction: 'Deny'
      bypass: 'AzureServices'
    }
  }
}

resource privateEndpoint 'Microsoft.Network/privateEndpoints@2025-01-01' = {
   name: storageAccount.name
   location: location
   properties: {
    privateLinkServiceConnections: [
      {
        name: storageAccount.name
        properties: {
          privateLinkServiceId: storageAccount.id
          groupIds: [
            'file'
          ]
        }
      }
    ]
    customNetworkInterfaceName: '${storageAccount.name}-nic'
    subnet: {
      id: virtualNetwork::privateEndpointSubnet.id
    }
   }
}

resource storageFileDataPrivilegedContributorReference 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = {
  name: roleNameStorageFileDataPrivilegedContributor
  scope: tenant()
}

resource roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
  name: guid(storageFileDataPrivilegedContributorReference.id, managedIdentity.id, storageAccount.id)
  scope: storageAccount
  properties: {
    principalId: managedIdentity.properties.principalId
    roleDefinitionId: storageFileDataPrivilegedContributorReference.id
    principalType: 'ServicePrincipal'
  }
}

resource privateDnsZone 'Microsoft.Network/privateDnsZones@2024-06-01' = {
  name: 'privatelink.file.core.windows.net'
  location: 'global'

  resource virtualNetworkLink 'virtualNetworkLinks' = {
    name: uniqueString(virtualNetwork.name)
    location: 'global'
    properties: {
      registrationEnabled: false
      virtualNetwork: {
        id: virtualNetwork.id
      }
    }
  }

  resource resRecord 'A' = {
    name: storageAccount.name
    properties: {
      ttl: 10
      aRecords: [
        {
          ipv4Address: first(first(privateEndpoint.properties.customDnsConfigs)!.ipAddresses)
        }
      ]
    }
  }
}

resource virtualNetwork 'Microsoft.Network/virtualNetworks@2025-01-01' = {
  name: vnetName
  location: location
  properties:{
    addressSpace: {
      addressPrefixes: [
        vnetAddressPrefix
      ]
    }
  }

  resource privateEndpointSubnet 'subnets' = {
    name: 'PrivateEndpointSubnet'
    properties: {
      addressPrefixes: [
        subnetEndpointAddressPrefix
      ]
    }
  }

  resource containerInstanceSubnet 'subnets' = {
    name: 'ContainerInstanceSubnet'
    properties: {
      addressPrefix: subnetACIAddressPrefix
      delegations: [
        {
          name: 'containerDelegation'
          properties: {
            serviceName: 'Microsoft.ContainerInstance/containerGroups'
          }
        }
      ]
    }
  }
}

resource privateDeploymentScript 'Microsoft.Resources/deploymentScripts@2023-08-01' = {
  name: deploymentScriptName
  dependsOn: [
    privateEndpoint
    privateDnsZone::virtualNetworkLink
  ]
  location: location
  kind: 'AzurePowerShell'
  identity: {
    type: 'UserAssigned'
    userAssignedIdentities: {
      '${managedIdentity.id}' : {}
    }
  }
  properties: {
    storageAccountSettings: {
      storageAccountName: storageAccount.name
    }
    containerSettings: {
      subnetIds: [
        {
          id: virtualNetwork::containerInstanceSubnet.id
        }
      ]
    }
    azPowerShellVersion: '14.0'
    retentionInterval: 'P1D'
    scriptContent: 'Write-Host "Hello World!"'
  }
}

ACI descarga imágenes de contenedor de Microsoft Container Registry. Si usa un firewall, agregue la dirección URL mcr.microsoft.com a la lista de permitidos para descargar la imagen. Si no se puede descargar la imagen del contenedor, la ACI pasa a un estado de waiting, lo que finalmente provoca un error de tiempo de espera.

Pasos siguientes

En este artículo, ha aprendido a ejecutar scripts de implementación mediante un punto de conexión privado. Para obtener más información: