tayganr/purviewdemo

Deployment error - client does not have authorization to perform action

Closed this issue · 24 comments

Got the below error when deploying. What step(s) is missing?
image

@leaubl - This error message likely means that the account being used to run the script does not have the necessary privileges needed from an Azure AD perspective to initialise the Service Principal. The Service Principal is used during the deployment process to create the Azure Purview account.

After MS Support’s advice and assistance to find out the Service Principal of object id '5627f484-079f-475d-bcd0-784b3039c1b1', grant the Service Principal Contributor role at Subscription level and rerun the deployment, purview account is created.

image

image

However, the deployment did not create the below Azure Resources as stated. Please advise how to proceed from here. Thanks!
image

@leaubl - If you navigate to the target Resource Group and click Deployments, what does the error message say?

image
Error on creation of Purview account and should be resolved after granting Contributor role to the Service Principal.

@leaubl - I suspect the Service Principal is not being created properly, likely due to lack of permissions. Can you try copying the code snippet below, replace YOUR_SUBSCRIPTION_ID and YOUR_RESOURCE_GROUP_NAME with your values, and paste the updated code snippet into Cloud Shell. See what error message comes back.

function createServicePrincipal([string]$subscriptionId, [string]$resourceGroupName, [string]$suffix) {
    $scope = "/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}"
    $sp = New-AzADServicePrincipal -DisplayName "pvDemoServicePrincipal-${suffix}" -Role "Owner" -Scope $scope
    Return $sp
}
$suffix = -join ((48..57) + (97..122) | Get-Random -Count 5 | ForEach-Object {[char]$_})
$subscriptionId = "YOUR_SUBSCRIPTION_ID"
$resourceGroupName = "YOUR_RESOURCE_GROUP_NAME"
$sp = createServicePrincipal $subscriptionId $resourceGroupName $suffix

hi Taygan,
Please see screenshot for the output of the script. Thanks.
image

hi Taygan,
Any update, please. Thanks.

@leaubl - The snippet you posted looks OK, the issue must be further down the script. We can copy/paste snippets from the PowerShell script to help narrow in on where the problem lies.

  1. Open Cloud Shell, and copy/paste the function code.
function getUserPrincipalId() {
    $principalId = $null
    Do {
        $emailAddress = Read-Host -Prompt "Please enter your Azure AD email address"
        $principalId = (Get-AzAdUser -Mail $emailAddress).id
        if ($null -eq $principalId) { $principalId = (Get-AzAdUser -UserPrincipalName $emailAddress).Id } 
        if ($null -eq $principalId) { Write-Host "Unable to find a user within the Azure AD with email address: ${emailAddress}. Please try again." }
    } until($null -ne $principalId)
    Return $principalId
}

function selectLocation() {
    $locationList='australiaeast', 'brazilsouth', 'canadacentral', 'centralindia', 'eastus', 'eastus2', 'southcentralus', 'southeastasia', 'uksouth', 'westeurope'
    $location = Get-Random -InputObject $locationList
    Return $location
}

function createServicePrincipal([string]$subscriptionId, [string]$resourceGroupName, [string]$suffix) {
    $scope = "/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}"
    $sp = New-AzADServicePrincipal -DisplayName "pvDemoServicePrincipal-${suffix}" -Role "Owner" -Scope $scope
    Return $sp
}

function getAccessToken([string]$tenantId, [string]$clientId, [string]$clientSecret, [string]$resource) {
    $requestAccessTokenUri = "https://login.microsoftonline.com/${tenantId}/oauth2/token"
    $body = "grant_type=client_credentials&client_id=${clientId}&client_secret=${clientSecret}&resource=${resource}"
    $accessToken = $null
    try {
        $token = Invoke-RestMethod -Method Post -Uri $requestAccessTokenUri -Body $body -ContentType 'application/x-www-form-urlencoded'
        $accessToken = $token.access_token
        Write-Host "Access token generated successfully!"
    } catch {
        Start-Sleep 1
        Write-Host "Pending access token..."
    }
    Return $accessToken
}

function deployTemplate([string]$accessToken, [string]$templateLink, [string]$resourceGroupName, [hashtable]$parameters) {
    $randomId = -join ((65..90) + (97..122) | Get-Random -Count 5 | ForEach-Object {[char]$_})
    $deploymentName = "deployment-${randomId}"
    $scope = "/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}"
    $deploymentUri = "https://management.azure.com${scope}/providers/Microsoft.Resources/deployments/${deploymentName}?api-version=2021-04-01"
    $deploymentBody = @{
        "properties" = @{
            "templateLink" = @{
                "uri" = $templateLink
            }
            "parameters" = $parameters
            "mode" = "Incremental"
        }
    }
    $params = @{
        ContentType = "application/json"
        Headers = @{"Authorization"="Bearer ${accessToken}"}
        Body = ($deploymentBody | ConvertTo-Json -Depth 9)
        Method = "PUT"
        URI = $deploymentUri
    }
    $job = Invoke-RestMethod @params
    Return $job
}

function getDeployment([string]$accessToken, [string]$subscriptionId, [string]$resourceGroupName, [string]$deploymentName) {
    $params = @{
        ContentType = "application/json"
        Headers = @{"Authorization"="Bearer ${accessToken}"}
        Method = "GET"
        URI = "https://management.azure.com/subscriptions/${subscriptionId}/resourcegroups/${resourceGroupName}/providers/Microsoft.Resources/deployments/${deploymentName}?api-version=2021-04-01"
    }
    $response = Invoke-RestMethod @params
    Return $response
}
  1. Once the functions have been copied, copy/paste one section at a time until an error occurs.

Section 1

# Variables
$tenantId = (Get-AzContext).Tenant.Id
$subscriptionId = (Get-AzContext).Subscription.Id
$principalId = getUserPrincipalId
$suffix = -join ((48..57) + (97..122) | Get-Random -Count 5 | ForEach-Object {[char]$_})
$location = selectLocation

# Create Resource Group
$resourceGroup = New-AzResourceGroup -Name "pvdemo-rg-${suffix}" -Location $location
$resourceGroupName = $resourceGroup.ResourceGroupName

# Create Service Principal
$sp = createServicePrincipal $subscriptionId $resourceGroupName $suffix
$clientId = $sp.AppId
$clientSecret = $sp.PasswordCredentials.SecretText

Section 2

$accessToken = $null
While ($null -eq $accessToken) {
    $accessToken = getAccessToken $tenantId $clientId $clientSecret "https://management.core.windows.net/"
}

Section 3

# Create Azure Purview Account (as Service Principal)
$templateLink = "https://raw.githubusercontent.com/tayganr/purviewdemo/main/templates/json/purviewdeploy.json" 
$parameters = @{ suffix = @{ value = $suffix } }
$deployment = deployTemplate $accessToken $templateLink $resourceGroupName $parameters
$deploymentName = $deployment.name

Section 4

$progress = ('.', '..', '...')
$provisioningState = ""
While ($provisioningState -ne "Succeeded") {
    Foreach ($x in $progress) {
        Clear-Host
        Write-Host "Deployment 1 of 2 is in progress, this will take approximately 5 minutes"
        Write-Host "Running${x}"
        Start-Sleep 1
    }
    $provisioningState = (getDeployment $accessToken $subscriptionId $resourceGroupName $deploymentName).properties.provisioningState
}

Section 5

# Deploy Template
$templateUri = "https://raw.githubusercontent.com/tayganr/purviewdemo/main/templates/json/azuredeploy.json"
$secureSecret = ConvertTo-SecureString -AsPlainText $sp.PasswordCredentials.SecretText
$job = New-AzResourceGroupDeployment `
  -Name "pvDemoTemplate-${suffix}" `
  -ResourceGroupName $resourceGroupName `
  -TemplateUri $templateUri `
  -azureActiveDirectoryObjectID $principalId `
  -servicePrincipalClientID $clientId `
  -servicePrincipalClientSecret $secureSecret `
  -suffix $suffix `
  -AsJob

$progress = ('.', '..', '...')
While ($job.State -eq "Running") {
    Foreach ($x in $progress) {
        Clear-Host
        Write-Host "Deployment 2 of 2 is in progress, this will take approximately 10 minutes"
        Write-Host "Running${x}"
        Start-Sleep 1
    }
}

Section 6

# # Clean-Up Service Principal
Remove-AzRoleAssignment -ResourceGroupName $resourceGroupName -ObjectId $sp.Id -RoleDefinitionName "Owner"
Remove-AzADServicePrincipal -ObjectId $sp.Id
Remove-AzADApplication -DisplayName $sp.DisplayName

Section 7

# # Clean-Up User Assigned Managed Identity
$configAssignment = Get-AzRoleAssignment -ResourceGroupName $resourceGroupName | Where-Object {$_.DisplayName.Equals("configDeployer")}
Remove-AzRoleAssignment -ResourceGroupName $resourceGroupName -ObjectId $configAssignment.ObjectId -RoleDefinitionName "Contributor"

Section 8

# Deployment Complete
$pv = (Get-AzResource -ResourceGroupName $resourceGroupName -ResourceType "Microsoft.Purview/accounts").Name
Clear-Host
Write-Host "Deployment complete! https://web.purview.azure.com/resource/${pv}`r`nNote: The Azure Data Factory pipeline and Azure Purview scans may still be running, these jobs will complete shortly."

@tayganr Section 7 encountered error
image

@tayganr , @leaubl , i believe this is due to the change to fix the $spn, it looks like the change is reverted back and $AppId is now back to $ApplicationId., and $sp.PasswordCredentials.SecretText back to $sp.Secret

image

can confirm i was able to deploy the resources successfully with the above change. However, running into a different issue with the clean up at the end:
image

@isdataninja - As documented here: Azure AD to Microsoft Graph migration changes in Azure PowerShell, the Az.Resources PowerShell module version 5.1.0 introduced changes to the identity-related cmdlets:

Changes to the ServicePrincipal Object

  • ApplicationId has been replaced by AppId

Changes to the ServicePrincipal Credential Object

  • Password has been replaced by SecretText

I believe you may be running an older version. This can be checked by running the following command:

Get-InstalledModule -Name "Az.Resources"

If we need to, let's raise this as a seperate issue and keep this thread focused on the original topic.

Original Error Message
The client '<client_id>' with object id '<object_id>' does not have authorization to perform action 'Microsoft.Purview/accounts/write' over scope '/subscriptions/<subscription_id>/resourcegroups/<resource_group>/providers/Microsoft.Purview/accounts/<purview_account_name>' or the scope is invalid.

In other words... the Service Principal that was created as part of the template deployment process did not have sufficient permissions to perform the action Microsoft.Purview/accounts/write within the scope of the target Subscription and Resource Group.

@leaubl - Final error message aside (as this may be a seperate issue), was the deployment successful? You can check this by navigating to the resource group and clicking Deployments. If there is an error message, can you please post it in this thread.

@tayganr deployment is successful with only Purview account created. Other resources are not there.

@tayganr any update?

@tayganr deployment is successful with only Purview account created. Other resources are not there.

@leaubl - If the Azure Purview account is the only resource that deployed, that means at least one of the deployments have failed. If you navigate to the target resource group and then click on "Deployments", it should reveal the failed deployment and related error message.

@tayganr
there is only1 deployment.
image

@leaubl - Based on our remote session, it appears the issue occurs during the section 5 code snippet, where deployment 2 of 2 is initiated to run as a background job. The script as it currently stands, expects the $job variable to immediately be in a "Running" state, but in your case, it is in a "Failed" state and therefore never runs.

I'll work on updating the script to better handle this scenario, in the meantime...

  1. Can you please copy and paste the code snippets one by one, from the start, until and including section 5.
  2. Once the code snippet in section 5 has run, can you please paste these new code snippets and share the response.

Command 1

$job.JobStateInfo | Format-List -Property *

Command 2

$job | Format-List -Property * 

@tayganr - below is the response for the 2 commands. Thanks.

PS /home/bee> $job.JobStateInfo | Format-List -Property *

State : Failed
Reason :


PS /home/bee> $job | Format-List -Property *

State : Failed
HasMoreData : True
Location : localhost
StatusMessage : Failed
CurrentPSTransaction :
Host : System.Management.Automation.Internal.Host.InternalHost
Command : New-AzResourceGroupDeployment
JobStateInfo : Failed
Finished : System.Threading.ManualResetEvent
InstanceId : c5cdbed9-da77-4afa-ae02-378b225e2b10
Id : 2
Name : Long Running Operation for 'New-AzResourceGroupDeployment' on resource 'pvDemoTemplate-z7aev'
ChildJobs : {}
PSBeginTime : 4/8/2022 1:25:06 AM
PSEndTime : 4/8/2022 1:25:07 AM
PSJobTypeName : AzureLongRunningJob`1
Output : {}
Error : {1:25:07 AM - Error: Code=InvalidTemplateDeployment; Message=The template deployment failed because of policy violation. Please see details for more information.
, 1:25:07 AM - Error: Code=RequestDisallowedByPolicy; Message=Resource 'pvdemoz7aevadls' was disallowed by policy. Policy identifiers: '[{"policyAssignment":{"name":
"CustomPurview","id":"/subscriptions/20d6eaa0-9bc1-49dc-be43-575a36c40752/providers/Microsoft.Authorization/policyAssignments/70b6f06669ce473bbea41bc2"},"policyDefin
ition":{"name":"CustomPurview","id":"/subscriptions/20d6eaa0-9bc1-49dc-be43-575a36c40752/providers/Microsoft.Authorization/policyDefinitions/905bff0f-250c-4e33-8168-
63b53e62e098"}}]'.
, The deployment validation failed}
Progress : {}
Verbose : {}
Debug : {[AzureLongRunningJob]: Starting cmdlet execution, setting for cmdlet confirmation required: 'False', [AzureLongRunningJob]: Error in cmdlet execution}
Warning : {}
Information : {}

@leaubl - Looks like there is an Azure Policy in your environment that is blocking the deployment.
Error: Code=RequestDisallowedByPolicy; Message=Resource 'pvdemoz7aevadls' was disallowed by policy.

Policy details:

{
   "policyAssignment":{
      "name":"CustomPurview",
      "id":"/subscriptions/20d6eaa0-9bc1-49dc-be43-575a36c40752/providers/Microsoft.Authorization/policyAssignments/70b6f06669ce473bbea41bc2"
   },
   "policyDefinition":{
      "name":"CustomPurview",
      "id":"/subscriptions/20d6eaa0-9bc1-49dc-be43-575a36c40752/providers/Microsoft.Authorization/policyDefinitions/905bff0f-250c-4e33-8168-63b53e62e098"
   }
}

@tayganr
There is only 1 policy as below.

Name: ASC Default (subscription: xxxxxxxxx)
Description: This is the default set of policies monitored by Azure Security Center. It was automatically assigned as part of onboarding to Security Center. The default assignment contains only audit policies. For more information please visit https://aka.ms/ascpolicies

Please advise how to overcome this. Thanks.

@leaubl - Inspecting the error message, the details seem to indicate that the name of the policy definition is CustomPurview.

[
   {
      "policyAssignment":{
         "name":"CustomPurview",
         "id":"/subscriptions/20d6eaa0-9bc1-49dc-be43-575a36c40752/providers/Microsoft.Authorization/policyAssignments/70b6f06669ce473bbea41bc2"
      },
      "policyDefinition":{
         "name":"CustomPurview",
         "id":"/subscriptions/20d6eaa0-9bc1-49dc-be43-575a36c40752/providers/Microsoft.Authorization/policyDefinitions/905bff0f-250c-4e33-8168-63b53e62e098"
      }
   }
]

Can you try:

  1. Navigate to Azure Policy in the Azure Portal
  2. Select Definitions
  3. Set the Scope to the Azure Subscription with Subscription ID 20d6eaa0-9bc1-49dc-be43-575a36c40752
  4. Ensure the Definition Type and Category are both set to all, then search for Definition ID 905bff0f-250c-4e33-8168-63b53e62e098
    image

If this is still returning blank, is it possible that your account may not have sufficient access to see all policy definitions?

@tayganr we deleted CustomPurview policy and the second deployment went through!!
All steps in Validate Deployment are fine except for step 6 - test connection to Azure SQL Database failed. Please advise further.
image

Thanks so much for making it this far. :)

@leaubl - As part of the script, there are API calls to automatically create and run scans against the two data sources. If you navigate to Data map > Sources and then click View details on the Azure SQL Database, is there a successful scan? Note: The Azure SQL Database is a serverless SKU, when testing the connection it may have gone to sleep, if you click Test connection multiple times it should wake up and start testing successfully again.

@leaubl - Speaking with @isantillan1 he rightly pointed out that the reason why the connection is failing is because the Lineage extraction (preview) feature is toggled ON and this requires additional permissions that are not automated as part of the script. If you toggle this OFF then test, it should be OK. I'm going to close this issue now as the original problem has been solved. This can be raised as a separate issue.