I encountered this error in the bicep file while creating function app with VNET integration. My Vnet is in another resource group named 'tst-vnet' separate from my function app. Is there a way to resolve this? Here is the code snippet:
resource functionApp 'Microsoft.Web/sites#2022-03-01' = {
name: functionAppName
location: location
tags: tags
kind: 'functionapp'
identity: {
type: 'SystemAssigned'
}
properties: {
httpsOnly: true
serverFarmId: functionAppHostingPlan.id
clientAffinityEnabled: true
publicNetworkAccess: 'Disabled'
siteConfig: {
appSettings: [
{
name: 'AzureWebJobsStorage'
value: 'DefaultEndpointsProtocol=https;AccountName=${functionAppStorageAccount.name};EndpointSuffix=${environment().suffixes.storage};AccountKey=${listKeys(functionAppStorageAccount.id, functionAppStorageAccount.apiVersion).keys[0].value}'
}
{
name: 'FUNCTIONS_EXTENSION_VERSION'
value: runtimeStackVersion
}
{
name: 'FUNCTIONS_WORKER_RUNTIME'
value: runtimeStack
}
{
name: 'WEBSITE_CONTENTAZUREFILECONNECTIONSTRING'
value: 'DefaultEndpointsProtocol=https;AccountName=${functionAppStorageAccount.name};EndpointSuffix=${environment().suffixes.storage};AccountKey=${listKeys(functionAppStorageAccount.id, functionAppStorageAccount.apiVersion).keys[0].value}'
}
{
name: 'WEBSITE_CONTENTSHARE'
value: 'functionapp'
}
{
name: 'WEBSITE_VNET_ROUTE_ALL'
value: '1'
}
{
name: 'WEBSITE_DNS_SERVER'
value: '168.63.129.16'
}
{
name: 'WEBSITE_CONTENTOVERVNET'
value: '1'
}
]
minTlsVersion: '1.2'
linuxFxVersion: linuxFxVersion
ftpsState: 'FtpsOnly'
}
}
}
I created a module to in order to use a resource group scope in order to reference the 'tst-vnet'
module networkConfig 'modules/network-config.bicep' = {
name: '${deploymentPrefix}-fn-networkcfg'
dependsOn: [
functionApp
]
scope: resourceGroup(pvtResourceGroupName)
params: {
functionAppName: functionAppName
privateBackendSubnet: privateBackendSubnet
privateEndpointVNet: privateEndpointVNet
privateBackendSubnetCIDR: privateBackendSubnetCIDR
}
}
This is the content of network-config.bicep
resource networkConfig 'Microsoft.Web/sites/networkConfig#2022-03-01' = {
name: '${functionAppName}/virtualNetwork'
properties: {
subnetResourceId: resourceId('Microsoft.Network/virtualNetworks/subnets', privateEndpointVNet, privateBackendSubnet)
swiftSupported: true
}
}
Full error from resource group deployment
{
"status": "Failed",
"error": {
"code": "DeploymentFailed",
"message": "At least one resource deployment operation failed. Please list deployment operations for details. Please see https://aka.ms/DeployOperations for usage details.",
"details": [
{
"code": "NotFound",
"message": "{\r\n "error": {\r\n "code": "ParentResourceNotFound",\r\n "message": "Can not perform requested operation on nested resource. Parent resource 'ase-data-tst-edp-fn-001' not found."\r\n }\r\n}"
}
]
When declaring a module, you can set a scope for the module that is different than the scope for the containing Bicep file. In your scenario, as you have existing virtual network, which is different scope, the virtual network should be declared in separate module. For your reference, you can use below template to deploy the function app integrating with virtual network which is in another resource group.
main.bicep file
#description('Location for all resources except Application Insights.')
param location string = resourceGroup().location
#description('Location for Application Insights.')
param appInsightsLocation string
#description('The language worker runtime to load in the function app.')
#allowed([
'node'
'dotnet'
'java'
])
param runtime string = 'node'
#description('The name of the function app that you wish to create.')
param appName string = 'fnapp${uniqueString(resourceGroup().id)}'
#description('Storage Account type')
#allowed([
'Standard_LRS'
'Standard_GRS'
'Standard_RAGRS'
])
param storageAccountType string = 'Standard_LRS'
#description('The name of the virtual network to be linked.')
param vnetName string
#description('The name of the subnet to be created within the virtual network.')
param subnetName string
#description('The name of the resource group where existing virtual network exists.')
param vnresourceGroup string
var functionAppName = appName
var hostingPlanName = appName
var storageAccountName = '${uniqueString(resourceGroup().id)}azfunctions'
var functionWorkerRuntime = runtime
resource virtualNetwork 'Microsoft.Network/virtualNetworks#2020-06-01' existing = {
name: vnetName
scope: resourceGroup(vnresourceGroup)
}
resource storageAccount 'Microsoft.Storage/storageAccounts#2021-04-01' = {
name: storageAccountName
location: location
sku: {
name: storageAccountType
}
kind: 'StorageV2'
}
resource appInsights 'Microsoft.Insights/components#2020-02-02' = {
name: functionAppName
location: appInsightsLocation
kind: 'web'
properties: {
Application_Type: 'web'
}
}
resource serverFarm 'Microsoft.Web/serverfarms#2020-06-01' = {
name: hostingPlanName
location: location
sku: {
name: 'EP1'
tier: 'ElasticPremium'
}
kind: 'elastic'
properties: {
maximumElasticWorkerCount: 20
}
}
resource function 'Microsoft.Web/sites#2020-06-01' = {
name: functionAppName
location: location
kind: 'functionapp'
properties: {
serverFarmId: serverFarm.id
siteConfig: {
appSettings: [
{
name: 'APPINSIGHTS_INSTRUMENTATIONKEY'
value: appInsights.properties.InstrumentationKey
}
{
name: 'APPLICATIONINSIGHTS_CONNECTION_STRING'
value: 'InstrumentationKey=${appInsights.properties.InstrumentationKey}'
}
{
name: 'AzureWebJobsStorage'
value: 'DefaultEndpointsProtocol=https;AccountName=${storageAccount.name};EndpointSuffix= ${environment().suffixes.storage};AccountKey=${storageAccount.listKeys().keys[0].value}'
}
{
name: 'WEBSITE_CONTENTAZUREFILECONNECTIONSTRING'
value: 'DefaultEndpointsProtocol=https;AccountName=${storageAccountName};EndpointSuffix=${environment().suffixes.storage};AccountKey=${storageAccount.listKeys().keys[0].value};'
}
{
name: 'FUNCTIONS_EXTENSION_VERSION'
value: '~3'
}
{
name: 'FUNCTIONS_WORKER_RUNTIME'
value: functionWorkerRuntime
}
{
name: 'WEBSITE_NODE_DEFAULT_VERSION'
value: '~12'
}
]
}
}
dependsOn: [
virtualNetwork
]
}
resource networkConfig 'Microsoft.Web/sites/networkConfig#2020-06-01' = {
parent: function
name: 'virtualNetwork'
properties: {
subnetResourceId: subnet.outputs.subnetId
swiftSupported: true
}
dependsOn: [
subnet
]
}
module subnet 'modules/subnet.bicep' = {
name: subnetName
scope: resourceGroup(vnresourceGroup)
params: {
subnetName: subnetName
subnetPrefix: '10.2.1.0/24'
vnetName: vnetName
}
}
subnet.bicep
#description('The name of the virtual network to be linked.')
param vnetName string
#description('The name of the subnet to be created within the virtual network.')
param subnetName string
#description('The address Prefix of subnet to be created within the virtual network.')
param subnetPrefix string
param deploySubnet bool = true
resource VNET 'Microsoft.Network/virtualNetworks#2021-02-01' existing = {
name: vnetName
}
resource Subnets 'Microsoft.Network/virtualNetworks/subnets#2020-11-01'= if (deploySubnet) {
name: subnetName
parent: VNET
properties: {
addressPrefix: subnetPrefix
privateEndpointNetworkPolicies: 'Disabled'
privateLinkServiceNetworkPolicies: 'Enabled'
delegations: [
{
name: 'Microsoft.Web.serverFarms'
properties: {
serviceName: 'Microsoft.Web/serverFarms'
}
}
]
}
}
output subnetId string = deploySubnet ? Subnets.id:''
Related
I am trying to get a logic app to read messages from a service bus topic. I am getting the error with the connection in logic app designer as :
Could not retrieve values. Error code: 'Unauthorized', Message: 'The remote server returned an error: (401) Unauthorized. Manage,EntityRead claims required for this operation. TrackingId:, SystemTracker:, Timestamp: clientRequestId: '. More diagnostic information: x-ms-client-request-id is ''.
currently I have tried the following in my cicd pipeline:
Grant permission to the logic app standard to access the connection api
resource servicebusConnectorAccessPolicy 'Microsoft.Web/connections/accessPolicies#2018-07-01-preview' = {
name: '${servicebusConnector.name}/${logicAppName}'
location: location
properties: {
principal: {
type: 'ActiveDirectory'
identity: {
tenantId: subscription().tenantId
objectId: logicApp.identity.principalId
}
}
}
}
Get this error:
ERROR: {"status":"Failed","error":{"code":"DeploymentFailed","message":"At least one resource deployment operation failed. Please list deployment operations for details. Please see https://aka.ms/DeployOperations for usage details.","details":[{"code":"BadRequest","message":"{\r\n "error": {\r\n "code": "InvalidTemplate",\r\n "message": "Unable to process template language expressions for resource '/subscriptions//resourceGroups//providers/Microsoft.Web/connections/servicebus/accessPolicies/' at line '124' and column '5'. 'The language expression property 'principalId' doesn't exist, available properties are 'type, userAssignedIdentities'.'",\r\n "additionalInfo": [\r\n {\r\n "type": "TemplateViolation",\r\n "info": {\r\n "lineNumber": 124,\r\n "linePosition": 5,\r\n "path": ""\r\n }\r\n }\r\n ]\r\n }\r\n}"}]}}
also:
resource logicAppServiceBusRoleAssignment 'Microsoft.Authorization/roleAssignments#2020-10-01-preview' = {
scope: serviceBusNamespace
name: guid('${serviceBusNamespaceName}-${roleDefinitionId}')
properties: {
principalType: 'ServicePrincipal'
roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleDefinitionId)
principalId: logicApp.identity.principalId
}
}
which yields this error:
ERROR: {"code": "InvalidTemplateDeployment", "message": "The template deployment failed with error: 'Authorization failed for template resource '' of type 'Microsoft.Authorization/roleAssignments'. The client '' with object id '' does not have permission to perform action 'Microsoft.Authorization/roleAssignments/write' at scope '/subscriptions//resourceGroups//providers/Microsoft.ServiceBus/namespaces//providers/Microsoft.Authorization/roleAssignments/'.'."}
for the role, it lacks permissions to add the role.
my logic app bicep file is as follows:
param logicAppName string // The name of the logic app to create.
param location string //Location for all resources.
param workflowDefinition object // the workflow schema in json
param connectorName string
param serviceBusNamespaceName string
resource managedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities#2022-01-31-preview' = {
name: <managedIDname>
location: location
}
resource servicebusConnector 'Microsoft.Web/connections#2016-06-01' = {
name: connectorName
location: location
kind: 'V2'
properties: {
api: {
id: concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/', resourceGroup().location, '/managedApis/servicebus')
}
displayName: connectorName
parameterValueSet: {
name: 'managedIdentityAuth'
values: {
namespaceEndpoint: {
value: '<endpoint>'
}
}
}
}
}
resource logicApp 'Microsoft.Logic/workflows#2019-05-01' = {
name: logicAppName
location: location
identity: {
type: 'UserAssigned'
userAssignedIdentities: {
'${managedIdentity.id}': {}
}
}
tags: {
displayName: logicAppName
}
properties: {
state: 'Enabled'
definition: workflowDefinition.definition
parameters: workflowDefinition.parameters
}
}
and for the service bus I have the following code:
param serviceBusNamespaceName string //Name of the Service Bus namespace
param serviceBusQueueName string // Name of the Queue
param location string // Location for all resources.
param topicApprovalName string // name of the topic for approval
param topicApprovalSubscriptionName string // name of the topic subscription
param connectorName string // name of the connector
param logicAppName string
param roleDefinitionId string = '8e3af657-a8ff-443c-a75c-2fe8c4bcb635'
resource logicApp 'Microsoft.Logic/workflows#2019-05-01' existing = {
name: logicAppName
}
resource lamanagedidentity 'Microsoft.ManagedIdentity/userAssignedIdentities#2022-01-31-preview' existing = {
name: 'managedIdentity'
}
resource serviceBusNamespace 'Microsoft.ServiceBus/namespaces#2022-01-01-preview' = {
name: serviceBusNamespaceName
location: location
sku: {
name: 'Standard'
}
identity: {
type: 'UserAssigned'
userAssignedIdentities: {
'${lamanagedidentity.id}': {}
}
}
properties: {}
}
resource serviceBusQueue 'Microsoft.ServiceBus/namespaces/queues#2022-01-01-preview' = {
parent: serviceBusNamespace
name: serviceBusQueueName
properties: {
lockDuration: 'PT5M'
maxSizeInMegabytes: 1024
requiresDuplicateDetection: false
requiresSession: false
defaultMessageTimeToLive: 'P10675199DT2H48M5.4775807S'
deadLetteringOnMessageExpiration: false
duplicateDetectionHistoryTimeWindow: 'PT10M'
maxDeliveryCount: 10
autoDeleteOnIdle: 'P10675199DT2H48M5.4775807S'
enablePartitioning: false
enableExpress: false
}
}
resource topic 'Microsoft.ServiceBus/namespaces/topics#2021-11-01' = {
name: topicApprovalName
parent: serviceBusNamespace
}
resource topicsubscription 'Microsoft.ServiceBus/namespaces/topics/subscriptions#2022-01-01-preview' = {
name: topicApprovalSubscriptionName
parent: topic
}
resource sbNamespaceAuthRules 'Microsoft.ServiceBus/namespaces/AuthorizationRules#2022-01-01-preview' = {
name: <auth name>
parent: serviceBusNamespace
properties: {
rights: [
'Listen'
'Manage'
'Send'
]
}
}
resource servicebusConnector 'Microsoft.Web/connections#2018-07-01-preview' = {
name: connectorName
location: location
kind: 'V2'
properties: {
api: {
id: '/subscriptions/${subscription().subscriptionId}/providers/Microsoft.Web/locations/${location}/managedApis/servicebus'
}
displayName: connectorName
parameterValueSet: {
name: <name>
values: {
namespaceEndpoint: {
value: '<endpoint>'
}
}
}
}
}
// Grant permission to the logic app standard to access the connection api
resource servicebusConnectorAccessPolicy 'Microsoft.Web/connections/accessPolicies#2018-07-01-preview' = {
name: '${servicebusConnector.name}/${logicAppName}'
location: location
properties: {
principal: {
type: 'ActiveDirectory'
identity: {
tenantId: subscription().tenantId
objectId: logicApp.identity.principalId
}
}
}
}
//output connectionRuntimeUrl string = reference(servicebusConnector.id, servicebusConnector.apiVersion, 'full').properties.connectionRuntimeUrl
Not sure why this isn't working... the issue that I'm landing on is the object id of this:
resource servicebusConnectorAccessPolicy 'Microsoft.Web/connections/accessPolicies#2018-07-01-preview' = {
name: '${servicebusConnector.name}/${logicAppName}'
location: location
properties: {
principal: {
type: 'ActiveDirectory'
identity: {
tenantId: subscription().tenantId
objectId: logicApp.identity.principalId
}
}
}
}
any help or advice would be welcome, I've been bashing my head against this for three days, and it just won't work.
thank you very much
I tried using a keyvault and connecting with a primary connection string, this did not work.
I have tried managed identity and roles, but both are not working for me.
it can find the connection, but gives an access error as cited above.
I was expecting for the connection to be authorized and for there to not be an error with the access policy.
For a project I want to deploy three related resources to Azure through Bicep templates: 1) App Service with System Assigned Managed Identity, 2) Key Vault and 3) Access policy for the App Service (step 1) to the Key Vault (step 2).
The AppService deployment outputs the principalId of the System Assigned Identity which is then later on used when deploying the KeyVaultAccessPolicy.
However, when I run the AZ CLI (az deployment sub create --location WestEurope --template-file ./main.bicep --parameters ./parameters/parameters-dev.json)
to deploy this to Azure I get the following error:
'The language expression property 'outputs' doesn't exist, available properties are 'templateHash, parameters, mode, provisioningState, timestamp, duration, correlationId, providers, dependencies, outputResources'.
Does anyone have an idea why referencing the principalId of the App Service does not work here? Many thanks for any help.
Modules and main.bicep:
main.bicep
module appService 'modules/appService.bicep' = {
name: 'deployAppService'
scope: resourceGroup(appServiceResourceGroup)
params: {
name: appServiceName
location: appServiceLocation
alwaysOn: appServiceAlwaysOn
apimIpAddress: appServiceApimIpAddress
appServicePlanResourceGroup: appServicePlanResourceGroup
appServicePlanName: appServicePlanName
}
}
module keyVault 'modules/keyVault.bicep' = {
name: 'deployKeyVault'
scope: resourceGroup(appServiceResourceGroup)
params: {
name: keyVaultName
dependsOn: [ appService ]
location: appServiceLocation
}
}
module keyVaultAccessPolicy 'modules/keyVaultAccessPolicy.bicep' = {
name: 'deployKeyVaultAccessPolicy'
scope: resourceGroup(appServiceResourceGroup)
params: {
name: '${appServiceName}-ap'
dependsOn: [ keyVault ]
objectId: appService.outputs.appServiceManagedIdentity
}
}
appService.bicep
resource appService 'Microsoft.Web/sites#2020-12-01' = {
name: name
location: location
kind: 'app'
identity: {
type: 'SystemAssigned'
}
properties: {
serverFarmId: '${subscription().id}/resourceGroups/${appServicePlanResourceGroup}/providers/Microsoft.Web/serverfarms/${appServicePlanName}'
enabled: true
}
}
output appServiceManagedIdentity string = appService.identity.principalId
keyVault.bicep
resource keyVault 'Microsoft.KeyVault/vaults#2022-07-01' = {
name: name
location: location
dependsOn: dependsOn
properties: {
enabledForDeployment: true
enabledForTemplateDeployment: true
enabledForDiskEncryption: true
tenantId: subscription().tenantId
accessPolicies: []
sku: {
name: 'standard'
family: 'A'
}
}
}
keyVaultAccessPolicy.bicep
resource keyVaultAccessPolicy 'Microsoft.KeyVault/vaults/accessPolicies#2022-07-01' = {
name: name
dependsOn: dependsOn
properties: {
accessPolicies: [
{
tenantId: subscription().tenantId
objectId: objectId
permissions: {
secrets: [
'get'
]
}
}
]
}
}
Already found the answer:
I missed that modules already contain an dependsOn property and so there is no need to pass the dependencies as param.
The KeyVaultAccessPolicy's name did not contain a reference to the parent resource (KeyVault), see name: '${keyVaultName}/add' below
See working modules and main.bicep below:
main.bicep
module appService 'modules/appService.bicep' = {
name: 'deployAppService'
scope: resourceGroup(appServiceResourceGroup)
params: {
name: appServiceName
location: appServiceLocation
alwaysOn: appServiceAlwaysOn
apimIpAddress: appServiceApimIpAddress
appServicePlanResourceGroup: appServicePlanResourceGroup
appServicePlanName: appServicePlanName
}
}
module keyVault 'modules/keyVault.bicep' = {
scope: resourceGroup(appServiceResourceGroup)
name: 'keyVaultDeploy'
params: {
location: appServiceLocation
name: keyVaultName
}
}
module keyVaultAccessPolicy 'modules/keyVaultAccessPolicy.bicep' = {
scope: resourceGroup(appServiceResourceGroup)
name: 'keyVaultAccessPolicyDeploy'
dependsOn: [
keyVault
]
params: {
keyVaultName: keyVaultName
objectId: appService.outputs.appServiceManagedIdentity
}
}
appService.bicep
resource appService 'Microsoft.Web/sites#2020-12-01' = {
name: name
location: location
kind: 'app'
identity: {
type: 'SystemAssigned'
}
properties: {
// left out
}
}
output appServiceManagedIdentity string = appService.identity.principalId
keyVault.bicep
resource keyVault 'Microsoft.KeyVault/vaults#2022-07-01' = {
name: name
location: location
properties: {
enabledForDeployment: true
enabledForTemplateDeployment: true
enabledForDiskEncryption: true
tenantId: subscription().tenantId
accessPolicies: []
sku: {
name: 'standard'
family: 'A'
}
}
}
keyVaultAccessPolicy.bicep
resource keyVaultAccessPolicy 'Microsoft.KeyVault/vaults/accessPolicies#2022-07-01' = {
name: '${keyVaultName}/add'
properties: {
accessPolicies: [
{
tenantId: subscription().tenantId
objectId: objectId
permissions: {
secrets: [
'get'
]
}
}
]
}
}
Just for a heads up you don't need to pass these stuffs with parameters.
Just use the existing tag with the scope it will be easier to get datafrom any resource.
https://learn.microsoft.com/en-us/azure/azure-resource-manager/bicep/existing-resource#different-scope
I created an Azure Function App by Bicep and tried to get the signalr_extension's value to use in the "upstream" configuration section of a serverless Azure SignalR Service. This is how I try to obtain this value in Bicep:
var signalRKey = listKeys(resourceId('Microsoft.Web/sites/host', funcAppName, 'default'), '2022-03-01').systemkeys.signalr_extension
This is how I configure the signalR service's upstream:
urlTemplate: 'https://${funcAppName}.azurewebsites.net/runtime/webhooks/signalr?code=${signalRKey}'
Running the bicep templates leads to the failure below:
Encountered an error (ServiceUnavailable) from host runtime.
When I remove the {signalRKey} from urlTemplate and replace it with a fictitious hard-coded value, the signalR is provisioned successfully.
The other thing that I noticed was that the singalr_extension key value was not populated after the function app was provisioned.
What am I missing in this exercise?
This feature is not available and feasible in App Service Plans. The signalr_extension must be populated manually after deploying the function app and signalR.
The signalr_extension is only created once you've deployed your function app with a SignalRTrigger function.
You can generate this key upfront if you're deploying the Function App and signalR service at the same time:
param functionAppName string
// Create the function app key for signalR
resource signalRKey 'Microsoft.Web/sites/host/systemkeys#2021-03-01' = {
name: '${functionAppName}/default/signalr_extension'
properties: {
name: 'signalr_extension'
}
}
The ARM API to generate function keys is just pointing to the function app API so it could take some time before it become available (see issue on github).
I managed to get this working consistently by deploying the systemkey and signalr using module.
Also for function app running on linux, the AzureWebJobsStorage setting is mandatory.
functionapp-systemkey.bicep module:
param functionAppName string
param keyName string
resource signalRKey 'Microsoft.Web/sites/host/systemkeys#2021-03-01' = {
name: '${functionAppName}/default/${keyName}'
properties: {
name: keyName
}
}
signalr.bicep module:
param location string = resourceGroup().location
param signalRName string
param functionAppName string
resource signalR 'Microsoft.SignalRService/signalR#2022-02-01' = {
name: signalRName
location: location
sku: {
name: 'Free_F1'
tier: 'Free'
capacity: 1
}
properties: {
features: [
{
flag: 'ServiceMode'
value: 'Serverless'
}
{
flag: 'EnableConnectivityLogs'
value: 'true'
}
]
cors: {
allowedOrigins: [
'*'
]
}
tls: {
clientCertEnabled: false
}
upstream: {
templates: [
{
hubPattern: '*'
eventPattern: '*'
categoryPattern: '*'
auth: {
type: 'None'
}
urlTemplate: 'https://${signalRName}.azurewebsites.net/runtime/webhooks/signalr?code=${listKeys(resourceId('Microsoft.Web/sites/host', functionAppName, 'default'), '2022-03-01').systemkeys.signalr_extension}'
}
]
}
}
}
main.bicep:
param location string = resourceGroup().location
param storageName string
param appServicePlanName string
param functionAppName string
param signalRName string
resource storage 'Microsoft.Storage/storageAccounts#2021-09-01' = {
name: storageName
location: location
kind: 'StorageV2'
sku: {
name: 'Standard_LRS'
}
properties: {
supportsHttpsTrafficOnly: true
minimumTlsVersion: 'TLS1_2'
}
}
resource appServicePlan 'Microsoft.Web/serverfarms#2021-03-01' = {
name: appServicePlanName
location: location
sku: {
name: 'Y1'
tier: 'Dynamic'
size: 'Y1'
family: 'Y'
capacity: 0
}
kind: 'functionapp'
properties: {
perSiteScaling: false
elasticScaleEnabled: false
maximumElasticWorkerCount: 1
isSpot: false
reserved: true
isXenon: false
targetWorkerCount: 0
targetWorkerSizeId: 0
zoneRedundant: false
}
}
resource functionApp 'Microsoft.Web/sites#2021-03-01' = {
name: functionAppName
location: location
kind: 'functionapp,linux'
properties: {
serverFarmId: appServicePlan.id
clientAffinityEnabled: false
clientCertEnabled: false
httpsOnly: true
siteConfig:{
linuxFxVersion: 'DOTNET|6.0'
use32BitWorkerProcess: true
ftpsState: 'FtpsOnly'
cors: {
allowedOrigins: [
'https://portal.azure.com'
]
supportCredentials: false
}
minTlsVersion: '1.2'
appSettings: [
{
name: 'FUNCTIONS_EXTENSION_VERSION'
value: '~4'
}
{
name: 'FUNCTIONS_WORKER_RUNTIME'
value: 'dotnet'
}
{
name: 'AzureWebJobsStorage'
value: 'DefaultEndpointsProtocol=https;AccountName=${storage.name};AccountKey=${listKeys(storage.id, '2019-06-01').keys[0].value};EndpointSuffix=core.windows.net;'
}
]
}
}
}
var signalrKeyName = 'signalr_extension'
module signalrKey 'modules/functionapp-systemkey.bicep' = {
name: '${functionAppName}-systemkey-${signalrKeyName}'
params: {
functionAppName: functionApp.name
keyName: signalrKeyName
}
}
module signalr 'modules/signalr.bicep' = {
name: signalRName
params: {
location: location
functionAppName: functionApp.name
signalRName: signalRName
}
dependsOn:[
signalrKey
]
}
I have created a basic Bicep template to deploy Azure Automation Acount. It contains a Runbook with the Powershell script and the linked Schedule. So far so good. The problem is to assign the Azure Role (Owner, Contributor, Reader) to the Managed Identity of this AA. I have all the needed values but have no idea how to put them together. To assign Azure Role via Bicep Template you should get the principalId of your Managed Identity of this AA which is pretty easy:
resource autaccount 'Microsoft.Automation/automationAccounts#2021-06-22'
**************************************************************************
output AutAccountPrincipalId string = autaccount.identity.principalId
My idea was to pass the value from this output to the parameter or variable and then use it in the next resource block. It turned out I can not pass the output as the parameter to the next resource block. Can someone assist me with this? The question is - how to use the value from one Bicep resource block in the other resource block?
This is the Bicep template to create Automation Account:
#description('Specifies the location for all resources.')
param location string = resourceGroup().location
var accountname = 'Snapshot'
var runbookname = 'CreateSnapshot'
var schedulename = 'SnapshotHourly'
resource autaccount 'Microsoft.Automation/automationAccounts#2021-06-22' = {
name: accountname
location: location
tags: {
test: 'true'
}
identity: {
type: 'SystemAssigned'
}
properties: {
disableLocalAuth: false
encryption: {
identity: {
}
keySource: 'Microsoft.Automation'
}
publicNetworkAccess: false
sku: {
capacity: null
family: null
name: 'Basic'
}
}
}
resource runbook1 'Microsoft.Automation/automationAccounts/runbooks#2019-06-01' = {
parent: autaccount
name: runbookname
location: location
properties: {
runbookType: 'PowerShell'
logVerbose: false
logProgress: false
logActivityTrace: 0
publishContentLink: {
uri: 'https://raw.githubusercontent.com/................'
}
}
}
resource schedule1 'Microsoft.Automation/automationAccounts/schedules#2020-01-13-preview' = {
parent: autaccount
name: schedulename
properties: {
startTime: '23:30'
expiryTime: ''
interval: 1
frequency: 'Hour'
timeZone: 'Europe/Riga'
}
}
resource link 'Microsoft.Automation/automationAccounts/jobSchedules#2020-01-13-preview' = {
name: guid('xxx05')
parent: autaccount
dependsOn: [
runbook1
]
properties: {
parameters: {}
runbook: {
name: runbookname
}
schedule: {
name: schedulename
}
}
}
output AutAccountPrincipalId string = autaccount.identity.principalId
The last resource block which actually assigns the Azure Role to MI is as follows:
#description('The principal to assign the role to')
param principalId string = 'abc897c3-ac9a-42e6-bc3f-xxxxxxxxxxxx'
#description('Built-in role to assign')
#allowed([
'Owner'
'Contributor'
'Reader'
])
//param builtInRoleType string = 'Owner'
#description('A new GUID used to identify the role assignment')
param roleNameGuid string = newGuid()
var Owner = '/subscriptions/${subscription().subscriptionId}/providers/Microsoft.Authorization/roleDefinitions/8e3af657-a8ff-443c-a75c-2fe8c4bcb635'
//var Contributor = '/subscriptions/${subscription().subscriptionId}/providers/Microsoft.Authorization/roleDefinitions/b24988ac-6180-42a0-ab88-20f7382dd24c'
//var Reader = '/subscriptions/${subscription().subscriptionId}/providers/Microsoft.Authorization/roleDefinitions/acdd72a7-3385-48ef-bd42-f606fba81ae7'
resource roleassignment 'Microsoft.Authorization/roleAssignments#2020-08-01-preview' = {
name: roleNameGuid
properties: {
principalId: principalId
roleDefinitionId: Owner
}
}
I acquired the principalID value manually from Portal but to automate the thing need it to pass from the blocks above, from output above or by some other way. Can someone assist with that?
Thank you in advance!
The updated code is:
#description('Specifies the location for all resources.')
param location string = resourceGroup().location
var accountname = 'SnapshotMgmtv11'
var runbookname = 'Create11'
var schedulename = 'SnapshotHourly11'
resource autaccount 'Microsoft.Automation/automationAccounts#2021-06-22' = {
name: accountname
location: location
tags: {
test: 'true'
}
identity: {
type: 'SystemAssigned'
}
properties: {
disableLocalAuth: false
encryption: {
identity: {
}
keySource: 'Microsoft.Automation'
}
publicNetworkAccess: false
sku: {
capacity: null
family: null
name: 'Basic'
}
}
}
resource runbook1 'Microsoft.Automation/automationAccounts/runbooks#2019-06-01' = {
parent: autaccount
name: runbookname
location: location
properties: {
runbookType: 'PowerShell'
logVerbose: false
logProgress: false
logActivityTrace: 0
publishContentLink: {
uri: 'https://raw.githubusercontent.com/..................'
}
}
}
resource schedule1 'Microsoft.Automation/automationAccounts/schedules#2020-01-13-preview' = {
parent: autaccount
name: schedulename
properties: {
startTime: '08:30'
expiryTime: ''
interval: 1
frequency: 'Hour'
timeZone: 'Europe/Riga'
}
}
resource link 'Microsoft.Automation/automationAccounts/jobSchedules#2020-01-13-preview' = {
name: guid('riniv011')
parent: autaccount
dependsOn: [
runbook1
]
properties: {
parameters: {}
runbook: {
name: runbookname
}
schedule: {
name: schedulename
}
}
}
#description('A new GUID used to identify the role assignment')
param roleNameGuid string = newGuid()
//var Owner = '/subscriptions/${subscription().subscriptionId}/providers/Microsoft.Authorization/roleDefinitions/8e3af657-a8ff-443c-a75c-2fe8c4bcb635'
var Contributor = '/subscriptions/${subscription().subscriptionId}/providers/Microsoft.Authorization/roleDefinitions/b24988ac-6180-42a0-ab88-20f7382dd24c'
//var Reader = '/subscriptions/${subscription().subscriptionId}/providers/Microsoft.Authorization/roleDefinitions/acdd72a7-3385-48ef-bd42-f606fba81ae7'
resource roleassignment 'Microsoft.Authorization/roleAssignments#2020-08-01-preview' = {
name: roleNameGuid
dependsOn: [
autaccount
]
properties: {
principalId: autaccount.identity.principalId
roleDefinitionId: Contributor
}
}
If you are deploying role assignement for resource group scope then you can use the something like below:
I tested it for creating only automation account and assigning the owner role at resource group for the system assigned identity of the automation account.
param location string = resourceGroup().location
var accountname = 'Snapshot'
resource autaccount 'Microsoft.Automation/automationAccounts#2021-06-22' = {
name: accountname
location: location
tags: {
test: 'true'
}
identity: {
type: 'SystemAssigned'
}
properties: {
disableLocalAuth: false
encryption: {
identity: {
}
keySource: 'Microsoft.Automation'
}
publicNetworkAccess: false
sku: {
capacity: null
family: null
name: 'Basic'
}
}
}
param roleNameGuid string = guid('Owner')
var Owner = '/subscriptions/${subscription().subscriptionId}/providers/Microsoft.Authorization/roleDefinitions/8e3af657-a8ff-443c-a75c-2fe8c4bcb635'
resource roleassignment 'Microsoft.Authorization/roleAssignments#2020-08-01-preview' = {
name: roleNameGuid
properties: {
principalId: autaccount.identity.principalId
roleDefinitionId: Owner
principalType:'ServicePrincipal'
}
}
Output:
Update:
For the below error :
Please add the principalType:'ServicePrincipal' in the role assignment block , as I have updated in the above code.
I have the below as the bicep template, I want to use the identity based connection, how can I build the template accordingly.
https://learn.microsoft.com/en-us/azure/azure-functions/functions-reference#connecting-to-host-storage-with-an-identity
I used the guidance here https://learn.microsoft.com/en-us/azure/azure-resource-manager/bicep/quickstart-create-bicep-use-visual-studio-code?tabs=PowerShell , for deployment.
New-AzResourceGroup -Name exampleRG -Location eastus
New-AzResourceGroupDeployment -ResourceGroupName exampleRG -TemplateFile ./main.bicep -storageName "{your-unique-name}"
But, I am getting error while addressing the template file - Code=InvalidTemplateDeployment; Message=The template deployment 'bicepeg' is
not valid according to the validation procedure
var baseName = uniqueString('identityRepro', subscription().id)
var location = 'uksouth'
resource stg 'Microsoft.Storage/storageAccounts#2019-06-01' = {
name: baseName
location: location
sku: {
name: 'Standard_LRS'
}
kind: 'StorageV2'
}
resource asp 'Microsoft.Web/serverfarms#2019-08-01' = {
name: baseName
location: location
sku: {
name: 'Y1'
tier: 'Dynamic'
}
}
resource ai 'Microsoft.Insights/components#2015-05-01' = {
name: baseName
location: location
kind: 'web'
properties: {
Application_Type: 'web'
}
}
resource fa 'Microsoft.Web/sites#2019-08-01' = {
name: baseName
location: location
identity: {
type: 'SystemAssigned'
}
properties: {
serverFarmId: asp.id
}
kind: 'functionapp'
resource appSettings 'config#2018-11-01' = {
name: 'appsettings'
properties: {
'AzureWebJobsStorage__accountName': stg.name
'FUNCTIONS_WORKER_RUNTIME': 'powershell'
'FUNCTIONS_WORKER_RUNTIME_VERSION': '~7'
'APPINSIGHTS_INSTRUMENTATIONKEY': ai.properties.InstrumentationKey
}
}
}
resource blobContrib 'Microsoft.Authorization/roleAssignments#2020-04-01-preview' = {
name: guid(fa.name, stg.name, 'ba92f.........d-a403-e96b0029c9fe')
properties: {
principalId: fa.identity.principalId
roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', 'ba92f.......-a403-e96b0029c9fe')
principalType: 'ServicePrincipal'
}
scope: stg
}
I think your problem is the id of the role. Roles are defined at subscription level, not in resource group. In your code instead resourceId function use subscriptionResourceId.
Update: as you clarified more on github issue, your additional problem was how the name is being constructed. uniqueString function generate an pseudo-random string (a hash) based on the seed - the parameters you provide to the function. when you give exactly this same values - you will get this same result.
Below code is working for me
var baseName = uniqueString(resourceGroup().id)
var location = 'uksouth'
resource stg 'Microsoft.Storage/storageAccounts#2019-06-01' = {
name: baseName
location: location
sku: {
name: 'Standard_LRS'
}
kind: 'StorageV2'
}
resource asp 'Microsoft.Web/serverfarms#2019-08-01' = {
name: baseName
location: location
sku: {
name: 'Y1'
tier: 'Dynamic'
}
}
resource ai 'Microsoft.Insights/components#2015-05-01' = {
name: baseName
location: location
kind: 'web'
properties: {
Application_Type: 'web'
}
}
resource fa 'Microsoft.Web/sites#2019-08-01' = {
name: baseName
location: location
identity: {
type: 'SystemAssigned'
}
properties: {
serverFarmId: asp.id
}
kind: 'functionapp'
resource appSettings 'config#2018-11-01' = {
name: 'appsettings'
properties: {
'AzureWebJobsStorage__accountName': stg.name
'FUNCTIONS_WORKER_RUNTIME': 'powershell'
'FUNCTIONS_WORKER_RUNTIME_VERSION': '~7'
'APPINSIGHTS_INSTRUMENTATIONKEY': ai.properties.InstrumentationKey
}
}
}
resource blobContrib 'Microsoft.Authorization/roleAssignments#2020-04-01-preview' = {
name: guid(fa.name, stg.name, 'ba92f5b4-2d11-453d-a403-e96b0029c9fe')
properties: {
principalId: fa.identity.principalId
roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe')
principalType: 'ServicePrincipal'
}
scope: stg
}
In your code use resourceGroup().id as uniqueString parameter - as it contains unique guid of your subscription and a resource group name, which has to be unique in the subscription - your hash also should be unique. providing only subscription().id will generate same string for all deployments to that subscription and resource group in it.