Search code examples
c#.netazureazure-resource-manager

How to create Role Assignment with ARM C# SDK?


Namely, how to create role assignment that can be used for different principals (but for the same scope/role)?

All examples that I found (for example this answer) use role definition ID for a role assignment name which causes clashes if I want to create another assignment for the same scope and role but for a different principal.

The examples of ARM templates used for this, clearly have this separation:

{
    "type": "Microsoft.Storage/storageAccounts/blobServices/containers/providers/roleAssignments",
    "apiVersion": "2018-09-01-preview",
    "name": "[concat(parameters('StorageAccountName'), '/default/',parameters('ContainerName'), '/Microsoft.Authorization/', parameters('roleNameGuid'))]",
    "properties": {
      "roleDefinitionId": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Authorization/roleDefinitions/', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe')]",
      "principalId": "[parameters('principalId2')]"
    }      
}

Here name and principalId are clearly different and you can just use a different name for the different principal.

However in C# SDK, the role definition is passed as a roleAssignmentName:

var roleAssignmentResource = armClient.GetRoleAssignmentResource(
        RoleAssignmentResource.CreateResourceIdentifier(
            scope: $"/subscriptions/{subscriptionId.ToString()}/resourceGroups/{resourceGroupName}",
            roleAssignmentName: "acdd72a7-3385-48ef-bd42-f606fba81ae7")); // this is the ID for the Reader role

Why does C# SDK combine them? How do I decouple them to be able to create multiple assignments for different principals?


Solution

  • Note that, role assignment name needs to be unique within a scope. If you use roleAssignmentName as role definition Id, it will create a clash when you are assigning role to multiple service principals under same scope.

    Alternatively, you can generate unique roleAssignmentName values by using Guid.NewGuid().ToString() to avoid conflicts.

    In my case, I used below modified code to create role assignments for different principals under same scope:

    using Azure;
    using Azure.Core;
    using Azure.Identity;
    using Azure.ResourceManager;
    using Azure.ResourceManager.Authorization;
    using Azure.ResourceManager.Authorization.Models;
    
    class Program
    {
        static async Task Main(string[] args)
        {
            TokenCredential cred = new DefaultAzureCredential();
            ArmClient client = new ArmClient(cred);
    
            string subscriptionId = "subId";
            string resourceGroupName = "rgname";
            string scope = $"/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}";
    
            // Define the role definition (Reader role in this case)
            string roleDefinitionId = "/subscriptions/subId/providers/Microsoft.Authorization/roleDefinitions/acdd72a7-3385-48ef-bd42-f606fba81ae7";
    
            string[] principalIds = new string[]
            {
                "userId01",
                "userId02"
            };
    
            foreach (var principalId in principalIds)
            {
                // Generate a GUID for the role assignment ID (it should be GUID based, not based on principalId)
                string roleAssignmentName = Guid.NewGuid().ToString();  // Use GUID directly as role assignment ID
    
                ResourceIdentifier roleAssignmentResourceId = RoleAssignmentResource.CreateResourceIdentifier(scope, roleAssignmentName);
    
                RoleAssignmentResource roleAssignment = client.GetRoleAssignmentResource(roleAssignmentResourceId);
    
                RoleAssignmentCreateOrUpdateContent content = new RoleAssignmentCreateOrUpdateContent(
                    new ResourceIdentifier(roleDefinitionId), Guid.Parse(principalId))
                {
                    PrincipalType = RoleManagementPrincipalType.User  // Set to ServicePrincipal if assigning to a service principal
                };
    
                // Create or update the role assignment
                ArmOperation<RoleAssignmentResource> lro = await roleAssignment.UpdateAsync(WaitUntil.Completed, content);
                RoleAssignmentResource result = lro.Value;
                RoleAssignmentData resourceData = result.Data;
    
                // Output the result
                Console.WriteLine("Role assignment created:");
                Console.WriteLine($"  Principal ID: {principalId}");
                Console.WriteLine($"  Role Definition ID: {roleDefinitionId}");
                Console.WriteLine($"  Assignment ID: {resourceData.Id}");
                Console.WriteLine(new string('-', 50));  // Separator line
            }
        }
    }
    

    Response:

    enter image description here

    To confirm that, I checked the same in Portal where role assigned successfully to users under same scope:

    enter image description here