Closed ghjklw closed 1 year ago
To elaborate on this issue, this is not a limitation of the API or Python SDK. I can achieve the same role assignment using the following Python code:
from azure.synapse.accesscontrol import AccessControlClient
from azure.identity import DefaultAzureCredential
import uuid
# Workspace
workspace_name = 'xxxx'
principal_id = "xxxx"
synapse_analytics_endpoint = ".dev.azuresynapse.net"
role_id="6e4bf58a-b8e1-4cc3-bbf9-d73143322b78" # Synapse Administrator
# Acquire a credential object
credential = DefaultAzureCredential()
acc = AccessControlClient(
credential=credential,
endpoint='{}{}{}'.format("https://", workspace_name, synapse_analytics_endpoint)
)
acc.role_assignments.create_role_assignment(
role_assignment_id=uuid.uuid5(),
principal_id=principal_id,
role_id=role_id,
scope="workspaces/%s" % workspace_name
)
route to CXP team
Same problem. I use bicep DeploymentScript task using the identity of user-assigned managed identity that is owner on the workspace (Azure RBAC) and fails to grant Synapse Administrator
Same problem. I use bicep DeploymentScript task using the identity of user-assigned managed identity that is owner on the workspace (Azure RBAC) and fails to grant Synapse Administrator
Exactly my use case 👍
I think I found the root cause: in the function _create_role_assignment
a call is made to _resolve_role_id
which in turns uses the function AccessControlClient.role_definitions.list_role_definitions()
. This API requires the permission Microsoft.Synapse/workspaces/read
and therefore fails.
Interestingly the API AccessControlClient.role_definitions.get_role_definition_by_id
does not require this permission...
The right fix would be to authorize this API for Azure RBAC owners of the Synapse workspace.
A simple temporary workaround is to assign the role by id (6e4bf58a-b8e1-4cc3-bbf9-d73143322b78) instead of role name (Synapse Administrator). The following command works:
az synapse role assignment create \
--workspace-name $workspaceName \
--role "6e4bf58a-b8e1-4cc3-bbf9-d73143322b78" \
--assignee-object-id $principalId \
--assignee-principal-type User
Thank you @ghjklw ! yes, your workaround works. I hit another issue that the command is not idempotent and fails the pipeline when executed twice, I have to check the existence of the role first. Created issue https://github.com/Azure/azure-cli/issues/25872
existingAssignment=$(az synapse role assignment list --workspace-name ${synapseName} --role "6e4bf58a-b8e1-4cc3-bbf9-d73143322b78" --assignee ${adGroup} --only-show-errors);
if [ -z "$existingAssignment" ];
then az synapse role assignment create --workspace-name ${synapseName} --role "6e4bf58a-b8e1-4cc3-bbf9-d73143322b78" --assignee ${adGroup} --only-show-errors;
fi
Thank you @ghjklw ! yes, your workaround works. I hit another issue that the command is not idempotent and fails the pipeline when executed twice, I have to check the existence of the role first. Created issue #25872
existingAssignment=$(az synapse role assignment list --workspace-name ${synapseName} --role "6e4bf58a-b8e1-4cc3-bbf9-d73143322b78" --assignee ${adGroup} --only-show-errors); if [ -z "$existingAssignment" ]; then az synapse role assignment create --workspace-name ${synapseName} --role "6e4bf58a-b8e1-4cc3-bbf9-d73143322b78" --assignee ${adGroup} --only-show-errors; fi
Sorry for the late reply! @mosharafMS, you're right, I also faced the same issue. Unfortunately, your workaround didn't work for me because az synapse role assignment list
also requires the Microsoft.Synapse/workspaces/read
RBAC.
Inspired by your solution, here's the workaround that I ended up using:
RESULT=$(az synapse role assignment create --workspace-name "$workspace" --role "$role" --assignee "$assignee" 2>&1)
EXIT_CODE=$?
if [ "$EXIT_CODE" != 0 ]; then
# If the error is of type "RoleAssignmentAlreadyExists", then it's fine
if [[ "$RESULT" == *"RoleAssignmentAlreadyExists"* ]]; then
echo "The Synapse role assignment already existed"
else
# If this was a different error, then print it to stderr and exit with the same error code
echo $RESULT 1>&2
exit $EXIT_CODE
fi
fi
echo $RESULT
For anyone reading this thread, here's a more complete working solution with Bicep:
// Create the Synapse workspace
resource synapse 'Microsoft.Synapse/workspaces@2021-06-01' = {
name: 'xxxx'
location: location
...
}
// Create a managed identity used for the role assignment during the deployment
resource userAssignedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = {
name: 'xxxx'
location: location
}
// Assign the Azure RBAC "Owner" role on the Synapse workspace to the managed identity just created
var roleDefinitionId = subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635') // Owner
resource identityRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
name: guid(subscription().subscriptionId, userAssignedIdentity.properties.principalId, roleDefinitionId)
scope: synapse
properties: {
principalId: userAssignedIdentity.properties.principalId
principalType: 'ServicePrincipal'
roleDefinitionId: roleDefinitionId
}
}
// Assign the Synapse RBAC role "Synapse Adminitrator" to a given principalId
resource synapseRoleAssignRBAC 'Microsoft.Resources/deploymentScripts@2020-10-01' = {
name: 'synapseRoleAssignAZ-${uniqueString(synapse.name, principalId)}'
kind: 'AzureCLI'
location: location
identity: {
type: 'UserAssigned'
userAssignedIdentities: {
'${userAssignedIdentity.properties.principalId}': {}
}
}
properties: {
forceUpdateTag: '1'
azCliVersion: '2.42.0'
retentionInterval: 'P1D'
environmentVariables: [
{
name: 'subscription'
value: subscription().subscriptionId
}
{
name: 'workspace'
value: synapse.name
}
{
name: 'role'
value: '6e4bf58a-b8e1-4cc3-bbf9-d73143322b78'
}
{
name: 'assignee'
value: principalId
}
]
scriptContent: '''
# az synapse role assignment is not idempotent and the output must therefore be verified
RESULT=$(az synapse role assignment create --workspace-name "$workspace" --role "$role" --assignee "$assignee" 2>&1)
EXIT_CODE=$?
if [ "$EXIT_CODE" != 0 ]; then
# If the error is of type "RoleAssignmentAlreadyExists", then it's fine
if [[ "$RESULT" == *"RoleAssignmentAlreadyExists"* ]]; then
echo "The Synapse role assignment already existed"
else
# If this was a different error, then print it to stderr and exit with the same error code
echo $RESULT 1>&2
exit $EXIT_CODE
fi
fi
echo $RESULT
'''
}
}
Thanks for the feedback! We are routing this to the appropriate team for follow-up. cc @wonner, @kevinzz6.
Author: | ghjklw |
---|---|
Assignees: | - |
Labels: | `bug`, `Service Attention`, `customer-reported`, `Synapse`, `CXP Attention`, `Auto-Assign` |
Milestone: | - |
Please create role assignment by role id directly if you are the Azure RBAC Owners, we will add it to cmdlet help doc.
I have tried to get the full Bicep example shown here to run. However, I get red squiggles for the references to "userAssignedIdentity.properties.principalId":
This expression is being used in an assignment to the "name" property of the "Microsoft.Authorization/roleAssignments" type, which requires a value that can be calculated at the start of the deployment. Properties of userAssignedIdentity which can be calculated at the start include "apiVersion", "id", "name", "type".bicep(BCP120)
Am I doing something wrong here? I basically just made a cut'n'paste of the full example in this thread.
My full code looks like this:
param synapseWorkspaceName string
param managedResourceGroupName string
param administratorGroupName string
param administratorGroupId string
param location string = resourceGroup().location
param tags object = {}
@allowed([
'None'
'SystemAssigned'
'SystemAssigned,UserAssigned'
])
param identity string = 'SystemAssigned'
param userAssignedIdentities object = {}
param allowAzureSynapseLinkforAzureSQLDatabaseToBypassFirewallRules bool = false
param initialWorkspaceAdminObjectId string = ''
param defaultDataLakeStorageName string
param createManagedPrivateEndpoint bool = false
param filesystem string = 'default'
param defaultDataLakeStorageResourceId string = ''
param encryption object = {}
param managedVirtualNetwork string = ''
param managedVirtualNetworkSettings object = {}
param privateEndpointConnections array = []
@allowed([
'Enabled'
'Disabled'
])
param publicNetworkAccess string = 'Disabled'
param purviewResourceId string = ''
param computeSubnetId string = ''
param workspaceRepositoryConfiguration object = {}
resource dst 'Microsoft.Storage/storageAccounts@2021-08-01' existing = {
name: defaultDataLakeStorageName
}
resource synAdmins 'Microsoft.Synapse/workspaces/administrators@2021-06-01' = {
name: 'activeDirectory'
parent: syn
properties: {
administratorType: 'ActiveDirectory'
login: administratorGroupName
sid: administratorGroupId
tenantId: subscription().tenantId
}
}
//enables the option "Allow Azure Synapse Link for Azure SQL Database to bypass firewall rules"
resource networkByPass 'Microsoft.Synapse/workspaces/trustedServiceByPassConfiguration@2021-06-01-preview' = {
name: 'default'
parent: syn
properties: {
trustedServiceBypassEnabled: allowAzureSynapseLinkforAzureSQLDatabaseToBypassFirewallRules
}
}
resource syn 'Microsoft.Synapse/workspaces@2021-06-01' = {
name: synapseWorkspaceName
location: location
tags: tags
identity: {
type: identity
userAssignedIdentities: empty(userAssignedIdentities) ? null : userAssignedIdentities
}
properties: {
cspWorkspaceAdminProperties: empty(initialWorkspaceAdminObjectId) ? null : {
initialWorkspaceAdminObjectId: initialWorkspaceAdminObjectId
}
defaultDataLakeStorage: {
accountUrl: dst.properties.primaryEndpoints.dfs
createManagedPrivateEndpoint: createManagedPrivateEndpoint
filesystem: filesystem
resourceId: empty(defaultDataLakeStorageResourceId) ? null : defaultDataLakeStorageResourceId
}
encryption: empty(encryption) ? null : encryption
managedResourceGroupName: managedResourceGroupName
managedVirtualNetwork: empty(managedVirtualNetwork) ? null : managedVirtualNetwork
managedVirtualNetworkSettings: empty(managedVirtualNetworkSettings) ? null : managedVirtualNetworkSettings
privateEndpointConnections: empty(privateEndpointConnections) ? null : privateEndpointConnections
publicNetworkAccess: publicNetworkAccess
purviewConfiguration: empty(purviewResourceId) ? null : {
purviewResourceId: purviewResourceId
}
virtualNetworkProfile: empty(computeSubnetId) ? null : {
computeSubnetId: computeSubnetId
}
workspaceRepositoryConfiguration: empty(workspaceRepositoryConfiguration) ? null : workspaceRepositoryConfiguration
}
}
// Create a managed identity used for the role assignment during the deployment
resource userAssignedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = {
name: 'xxxx'
location: location
}
// Assign the Azure RBAC "Owner" role on the Synapse workspace to the managed identity just created
var roleDefinitionId = subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635') // Owner
resource identityRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
name: guid(subscription().subscriptionId, userAssignedIdentity.properties.principalId, roleDefinitionId)
scope: syn
properties: {
principalId: userAssignedIdentity.properties.principalId
principalType: 'ServicePrincipal'
roleDefinitionId: roleDefinitionId
}
}
// Assign the Synapse RBAC role "Synapse Adminitrator" to a given principalId
resource synapseRoleAssignRBAC 'Microsoft.Resources/deploymentScripts@2023-08-01' = {
name: 'synapseRoleAssignAZ'
kind: 'AzureCLI'
location: location
identity: {
type: 'UserAssigned'
userAssignedIdentities: {
'${userAssignedIdentity.properties.principalId}': {
}
}
}
properties: {
forceUpdateTag: '1'
azCliVersion: '2.42.0'
cleanupPreference: 'OnExpiration'
retentionInterval: 'P1D'
environmentVariables: [
{
name: 'subscription'
value: subscription().subscriptionId
}
{
name: 'workspace'
value: syn.name
}
{
name: 'role'
value: '6e4bf58a-b8e1-4cc3-bbf9-d73143322b78'
}
{
name: 'assignee'
value: administratorGroupId
}
]
scriptContent: '''
# az synapse role assignment is not idempotent and the output must therefore be verified
RESULT=$(az synapse role assignment create --workspace-name "$workspace" --role "$role" --assignee "$assignee" 2>&1)
EXIT_CODE=$?
if [ "$EXIT_CODE" != 0 ]; then
# If the error is of type "RoleAssignmentAlreadyExists", then it's fine
if [[ "$RESULT" == *"RoleAssignmentAlreadyExists"* ]]; then
echo "The Synapse role assignment already existed"
else
# If this was a different error, then print it to stderr and exit with the same error code
echo $RESULT 1>&2
exit $EXIT_CODE
fi
fi
echo $RESULT
'''
}
}
output principalId string = syn.identity.principalId
output name string = syn.name
output id string = syn.id
Hi @jesperrula data plane resources like role assignment does not support bicep.
Please create role assignment by role id directly if you are the Azure RBAC Owners, we will add it to cmdlet help doc.
This role is not Azure RBAC, the proper solution here would be to add support for it in native Synapse bicep - any chance you could get in touch with the product team about it?
Command Name az synapse role assignment create
Describe the bug It should be possible to add a Synapse Administrator to Synapse RBAC if you are an owner of the Synapse workspace in Azure RBAC even though you are not a System Administrator. This works using Synapse Studio, but not using Azure CLI (or PowerShell module).
Sources:
This is relevant in a CI/CD context, to be able to automate the assignment of Synapse RBAC roles: there is no way with ARM or Bicep to control who is originally granted the "Synapse Administrator" role (stackoverflow)
Errors:
To Reproduce
Expected behavior I would expect the role to be successfully assigned if the principal used has the "Owner" role for the workspace in Azure RBAC (or any other role with the ability to manage Azure RBAC), even though it does not have the permission
Microsoft.Synapse/workspaces/read
Environment summary