bicep icon indicating copy to clipboard operation
bicep copied to clipboard

"Unable to evaluate template language function 'extensionResourceId'" when referencing role definition property

Open johanredeploy opened this issue 2 years ago • 0 comments

Bicep version

  • Bicep CLI version 0.10.61 (8f44805506)
  • vscode Bicep extension version v0.10.61
  • az cli versions
{
  "azure-cli": "2.40.0",
  "azure-cli-core": "2.40.0",
  "azure-cli-telemetry": "1.0.8",
  "extensions": {
    "account": "0.2.3",
    "authV2": "0.1.1",
    "ssh": "1.1.2"
  }
}

Describe the bug Referencing properties from an existing role definition and passing that property to a module fails.

For this use case, I want to iterate assignable scopes for a role to allow reading individual secrets in a key vault. The intention is to create reusable modules to be able to read individual secrets in a key vault.

I have created a role definition that looks like this:

{
    "assignableScopes": [
      "/subscriptions/xxx/resourcegroups/rg-01-xxx-xxx-euw-dev/providers/Microsoft.KeyVault/vaults/kv-01-xxx-xxx-dev/secrets/secret1",
      "/subscriptions/xxx/resourcegroups/rg-01-xxx-xxx-euw-dev/providers/Microsoft.KeyVault/vaults/kv-01-xxx-xxx-dev/secrets/secret2",
      "/subscriptions/xxx/resourcegroups/rg-01-xxx-xxx-euw-dev/providers/Microsoft.KeyVault/vaults/kv-01-xxx-xxx-dev/secrets/secret3",
      "/subscriptions/xxx/resourcegroups/rg-01-xxx-xxx-euw-dev/providers/Microsoft.KeyVault/vaults/kv-01-xxx-xxx-dev/secrets/secret4",
      "/subscriptions/xxx/resourcegroups/rg-01-xxx-xxx-euw-dev/providers/Microsoft.KeyVault/vaults/kv-01-xxx-xxx-dev/secrets/secret5",
      "/subscriptions/xxx/resourcegroups/rg-01-xxx-xxx-euw-dev/providers/Microsoft.KeyVault/vaults/kv-01-xxx-xxx-dev/secrets/secret6"
    ],
    "description": "Allows reading specific secrets in the xxx key vault in mgmt group xxx",
    "id": "/subscriptions/xxx/providers/Microsoft.Authorization/roleDefinitions/xxx",
    "name": "xxx",
    "permissions": [
      {
        "actions": [],
        "dataActions": [
          "Microsoft.KeyVault/vaults/secrets/getSecret/action",
          "Microsoft.KeyVault/vaults/secrets/readMetadata/action"
        ],
        "notActions": [],
        "notDataActions": []
      }
    ],
    "roleName": "Limitied xxx secret reader",
    "roleType": "CustomRole",
    "type": "Microsoft.Authorization/roleDefinitions"
  }

I then in a later step in a workflow retrieve the role using the existing keyword and iterate the assignable scopes for a single principal. When retrieving the assignable scopes, bicep fails with:

Unable to evaluate template language function 'extensionResourceId': function requires exactly two multi-segmented arguments. The first must be the parent resource id while the second must be resource type including resource provider namespace. Current function arguments '/providers/Microsoft.Management/managementGroups/ESD,Microsoft.Authorization/roleDefinitions,/subscriptions/xxx/providers/Microsoft.Authorization/roleDefinitions/xxx'. 

To Reproduce Steps to reproduce the behaviour:

1. Create a custom role with multiple assignable secret scopes This step works, just intended to be able to reproduce.

targetScope = 'subscription'

param subscriptionId string
param resourceGroupName string
param keyVaultName string
param allowedSecrets array

var keyVaultScope = '/subscriptions/${subscriptionId}/resourcegroups/${resourceGroupName}/providers/Microsoft.KeyVault/vaults/${keyVaultName}'

var assignableScopes = [for secretName in allowedSecrets: '${keyVaultScope}/secrets/${secretName}']

var roleName = 'Limitied ${keyVaultName} secret reader'

// Permissions based on Key Vault Secrets User
// https://www.azadvertizer.net/azrolesadvertizer/4633458b-17de-408a-b874-0445c86b69e6.html
resource key_vault_secrets_user_role_definition 'Microsoft.Authorization/roleDefinitions@2018-01-01-preview' existing = {
  name: '4633458b-17de-408a-b874-0445c86b69e6'
}

resource role_definition 'Microsoft.Authorization/roleDefinitions@2018-01-01-preview' = {
  name: guid(roleName)
  properties: {
    roleName: roleName
    description: 'Allows reading specific secrets in the ${keyVaultName} key vault'
    assignableScopes: assignableScopes
    permissions: key_vault_secrets_user_role_definition.properties.permissions
  }
}

output roleDefinitionId string = role_definition.id

2. Assign the role to a single principal main.bicep

targetScope = 'managementGroup'

param secretReaderRoleDefinitionId string // This is the output from above declared definition
param subscriptionId string
param resourceGroupName string
param keyVaultName string

resource secretReaderRoleDefinition 'Microsoft.Authorization/roleDefinitions@2018-01-01-preview' existing = {
    name: secretReaderRoleDefinitionId
}
  
module regressionTestSecretReader 'module.bicep' = {
  name: 'secret-reader-role-assignment'
  scope: resourceGroup(subscriptionId, resourceGroupName)
  params: {
    secretReaderRoleDefinitionId: secretReaderRoleDefinitionId
    assignableScopes: secretReaderRoleDefinition.properties.assignableScopes
    managementGroupName: managementGroup().name
    keyVaultName: keyVaultName
  }
}

// Lots of other assignments done on the management group level

module.bicep

targetScope = 'resourceGroup'

param secretReaderRoleDefinitionId string
param assignableScopes array = []
param managementGroupName string
param keyVaultName string

var regressionTestSPObjectId = managementGroupName == 'ES' ? 'abc' : 'xyz'

// Full scope looks like this:
// '/subscriptions/<sub>/resourcegroups/<rg>/providers/Microsoft.KeyVault/vaults/<vault>/<secret>'
// Hence 8 is the secret name
var secretNames = [for scope in assignableScopes: split(scope, '/')[8]]
resource secretResources 'Microsoft.KeyVault/vaults/secrets@2021-11-01-preview' existing = [for secret in secretNames: {
  name: '${keyVaultName}/${secret}'
}]

// Iterating the secretResources array is not supported, so we iterate the scope which they are based
resource regressionTestKeyVaultReaderAssignment 'Microsoft.Authorization/roleAssignments@2020-04-01-preview' = [for (scope, index) in assignableScopes: {
  name: guid(managementGroupName, regressionTestSPObjectId, scope)
  scope: secretResources[index] // Access by index and apply this role assignment to all assignable scopes
  properties: {
    principalId: regressionTestSPObjectId
    roleDefinitionId: secretReaderRoleDefinitionId
  }
}]

3. Run the deployment

az deployment mg create --name 'role-assignments-deployment-local' \
            --location 'westeurope' \
            --management-group-id mgmtgroup \
            --template-file ./role-assignments/main.bicep \
            --parameters secretReaderRoleDefinitionId="/subscriptions/<same-as-role-definition-and-kv>/providers/Microsoft.Authorization/roleDefinitions/<role-def-id>" \
            --parameters resourceGroupName="my-rg" \
            --parameters subscriptionId="some-as-role-def-and-kv" \
            --parameters keyVaultName="my-kv"

4. Error output

{"code": "InvalidTemplate", "message": "Deployment template validation failed: 'The template resource 'secret-reader-role-assignment' at line '26' and column '5' is not valid: Unable to evaluate template language function 'extensionResourceId': function requires exactly two multi-segmented arguments. The first must be the parent resource id while the second must be resource type including resource provider namespace. Current function arguments '/providers/Microsoft.Management/managementGroups/ESD,Microsoft.Authorization/roleDefinitions,/subscriptions/xxx/providers/Microsoft.Authorization/roleDefinitions/xxx'. Please see https://aka.ms/arm-template-expressions/#extensionresourceid for usage details.. Please see https://aka.ms/arm-template-expressions for usage details.'.", "additionalInfo": [{"type": "TemplateViolation", "info": {"lineNumber": 26, "linePosition": 5, "path": "properties.template.resources[0]"}}]}

Additional context

Compile to ARM bicep build main.bicep

Error seems to originate from row 42 in the compiled template, the only mention of extensionResourceId.

"assignableScopes": {
  "value": "[reference(extensionResourceId(managementGroup().id, 'Microsoft.Authorization/roleDefinitions', parameters('secretReaderRoleDefinitionId')), '2018-01-01-preview').assignableScopes]"
},

Docs clearly state that the failing function accepts 3 arguments.

Removing the first argument from this parameter (managementGroup().id) gives an invalid template according to vscode:

"assignableScopes": {
  "value": "[reference(extensionResourceId('Microsoft.Authorization/roleDefinitions', parameters('secretReaderRoleDefinitionId')), '2018-01-01-preview').assignableScopes]"
},

The function 'extensionResourceId' takes at least 3 arguments.

johanredeploy avatar Sep 29 '22 10:09 johanredeploy