The Problem

Azure Lighthouse is based on something called ‘Azure delegated resource management’. What this means is that an identity, a principal, in tenant A has permissions on Azure resources in tenant B. This works at the control plane in Azure, which of course is Azure Resource Manager or ARM for short. Consequently, Azure Lighthouse only works for permissions on the control plane, not the data plane. If you are familiar with Azure RBAC it means you can only use ‘actions’ and not ‘dataActions’.

This shortcoming, to me, is the biggest pain point with Azure Lighthouse.

We have customer tenants where we only have access to with Azure Lighthouse. This is troublesome with, for example, granting Key vault permissions to an application (a Logic App / Function app / VM). When using the Azure Portal, the Azure resources of the ‘customer tenant’ are projected into the portal logged in with my ‘managing tenant’ user. This works great for the most part, except for blades where we need to pick an identity from Azure AD as it is looking in the managing tenant Azure AD instead of the customer tenants Azure AD.

So how can we overcome this?


Azure Keyvault has 2 RBAC models:

  • Vault access policy model
  • Azure RBAC access control permission model

When creating the Key Vault, there is an option to choose the permission model
When creating the Key Vault, there is an option to choose the permission model

Here is a migration guide to migrate from the old vault access policy model to the newer Azure RBAC permission model.

When using Azure Lighthouse, managing the vault itself is done using control plane actions. Setting an Azure RBAC permission is also a control plane action. Creating, reading, updating and deleting a secret, certificate or key on the other hand are data plane actions. This is also true for setting permissions on this level with the vault access policy model. It’s a resource provider specific access model compared to Azure RBAC. In short this means we cannot utilize the vault access policy model to fix the problem, but Azure RBAC might just do the trick!


The Lighthouse Delegation

There are 2 built-in roles within Azure to assign Azure RBAC roles, Owner and User Access Administrator. Azure Lighthouse does not permit the use of owner permissions when delegating resources at all. User access administrator is not permitted too, except for assigning permissions to a managed identity within the customer tenant.

Role assignments must use Azure built-in roles. All built-in roles are currently supported with Azure Lighthouse, except for Owner or any built-in roles with DataActions permission. The User Access Administrator role is supported only for limited use in assigning roles to managed identities. Custom roles and classic subscription administrator roles are not supported.

Ok. Great. This means we should be able to set an Azure RBAC role with permissions to the data plane of an Azure Key Vault to a Managed Identity in the customer tenant. But how exactly?

We will have to define the User Access Administrator role in the Azure Lighthouse delegation. But not only that, we’ll also have to specify which built-in role this identity can actually assign to the Managed Identities.

The trick is filling in the correct Authorization details like this:

 1var auths = [
 2    {
 3        "principalId": "00000000-0000-0000-0000-000000000000", //The objectId of the principal in the managing tenant to delegate to. Could be a User, SPN or group.
 4        "principalIdDisplayName": "Group that can grant permissions to customer managed identities",
 5        "roleDefinitionId": "18d7d88d-d35e-4fb5-a5c3-7773c20a72d9",
 6        "delegatedRoleDefinitionIds": [
 7            "00482a5a-887f-4fb3-b363-3b7fe8e74483",
 8            "4633458b-17de-408a-b874-0445c86b69e6"
 9        ]
10    }

We will have to specify the guid of the User Access Administrator (18d7d88d-d35e-4fb5-a5c3-7773c20a72d9) and the roles this delegated identity can grant to managed identities. For the sake of this demo, lets say Key Vault Administrator (00482a5a-887f-4fb3-b363-3b7fe8e74483), the role to manage everything within a keyvault, and Key Vault Secrets User (4633458b-17de-408a-b874-0445c86b69e6), the role to read secrets.

So, now this would suffice, right? When testing myself, it still did not work when setting Azure RBAC permissions on a Key Vault in the customer environment using the Azure Portal. Permission denied.

There are 2 more things:

  • It does not work in the portal, but granting permissions with bicep/ARM template does work.
  • The user with Lighthouse permission need to have contributor access to the resource where the RBAC permissions will be set. A simple fix (but could be a bit of a broad scope) would be to add contributor to the autorization array. This would give this bicep template to create the Lighthouse delegation:
 2targetScope = 'subscription'
 4@description('Specify the objectId of the principal to delegate to. Could be a Group, SPN, MI or User.')
 5param principalObjectId string
 7@description('Specify a unique name for your offer')
 8param mspOfferName string = 'myOfferName'
10@description('Name of the Managed Service Provider offering')
11param mspOfferDescription string = mspOfferName
13@description('Specify the tenant id of the Managed Service Provider')
14param managedByTenantId string
16@description('Specify the Azure Lighthouse authorizations')
17param authorizations array = [
18  {
19    'principalId': principalObjectId
20    'roleDefinitionId': 'b24988ac-6180-42a0-ab88-20f7382dd24c'
21    'principalIdDisplayName': 'Group with contributor permissions that can grant permissions to customer managed identities'
22  }
23  {
24    'principalId': principalObjectId
25    'principalIdDisplayName': 'Group with contributor permissions that can grant permissions to customer managed identities'
26    'roleDefinitionId': '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9'
27    'delegatedRoleDefinitionIds': [
28        '00482a5a-887f-4fb3-b363-3b7fe8e74483'
29        '4633458b-17de-408a-b874-0445c86b69e6'
30    ]
31  }
34var mspRegistrationName_var = guid(mspOfferName)
35var mspAssignmentName_var = guid(mspOfferName)
37resource mspRegistrationName_resource 'Microsoft.ManagedServices/registrationDefinitions@2019-09-01' = {
38  name: mspRegistrationName_var
39  properties: {
40    registrationDefinitionName: mspOfferName
41    description: mspOfferDescription
42    managedByTenantId: managedByTenantId
43    authorizations: authorizations
44  }
47resource mspAssignmentName_resource 'Microsoft.ManagedServices/registrationAssignments@2019-09-01' = {
48  name: mspAssignmentName_var
49  properties: {
50    registrationDefinitionId:
51  }

Now we’re halfway there. The user/app that needs to work in the customer tenant from the managing tenant has access and the necessary permissions to hand out Key Vault permissions to Managed Identities.

Setting the Key Vault permissions

So, as stated before setting the key vault permissions trhough the Azure Portal does not work. It seems it should work as it does show the correct managed identities (the ones in the customer tenant instead of the managing tenant), but I could not get it to work. Permission denied. Somewhere in the docs it states its not supported in the Azure portal.

It works with an ARM template or bicep though. Here’s a bicep example on setting permissions on an Azure Key Vault:

 1targetScope = 'resourceGroup'
 3@description('Specify the SPN objectId of the managed Identity to grant access to. Find this id in Azure AD under Enterprise Apps')
 4param principalId string = '82b2d89c-9a4e-4bb0-8d1a-9d7f8964f6f7'
 6@description('Specify the resource ID of the resource with the managed identity, in this case its a user assigned managed identity.')
 7param delegatedManagedIdentityResourceId string = '/subscriptions/{subscriptionIdGuid}/resourcegroups/{rgName}/providers/Microsoft.ManagedIdentity/userAssignedIdentities/{resourceName}'
 9@description('Specify the id of the Azure Role to grant')
10param roleDefinitionId string = '4633458b-17de-408a-b874-0445c86b69e6'
12@description('Principal type of the assignee.')
14  'Device'
15  'ForeignGroup'
16  'Group'
17  'ServicePrincipal'
18  'User'
20param principalType string = 'ServicePrincipal' //To assign to managed identity, use 'ServicePrincipal'
22@description('Specify the name of the key vault to grant access to')
23param keyVaultName string
25var roleAssignmentName_var = guid(keyVaultName)
27resource keyVault_resource 'Microsoft.KeyVault/vaults@2021-10-01' existing = {
28  name: keyVaultName
31resource roleDefinition_resource 'Microsoft.Authorization/roleDefinitions@2018-01-01-preview' existing = {
32  scope: resourceGroup()
33  name: roleDefinitionId
36resource roleAssignment_resource 'Microsoft.Authorization/roleAssignments@2020-10-01-preview' = {
37  name: roleAssignmentName_var
38  scope: keyVault_resource
39  properties: {
40    principalId: principalId
41    roleDefinitionId:
42    delegatedManagedIdentityResourceId: delegatedManagedIdentityResourceId
43    principalType: principalType
44  }

Now, we have authorized the Managed Identity to read the secrets in the Key Vault. It does not show in the Azure portal though, when looking at the Access control (IAM) pages with Azure Lighthouse. With a tenant local account the permissions are visible though.

To summarize:

  • Works only when the Key Vault is using Azure RBAC permission model
  • Need to specify the correct authorization array when creating the Lighthouse delegation. Keep in mind you’ll also have to specify on beforehand which permissions can be granted by the principal with Lighthouse delegated permissions
  • The Azure Lighthouse permissions need to include contributor permissions on the resources where the principal with Lighthouse delegated permissions need to assign any permissions
  • Works only for granting permissions to Managed Identities
  • This example uses Key Vault, but the same can be applied to any Azure RBAC role. Key Vault is a good example as it really shows the difference and consequences of control plane vs data plane.