Azure Runbook can't modify Azure AD application - azure

I'm trying to execute this in an Azure Automation runbook
$app = Get-AzureADApplication -ObjectId $ApplicationId
$appRole = New-Object Microsoft.Open.AzureAD.Model.AppRole
$appRole.AllowedMemberTypes = New-Object System.Collections.Generic.List[string]
$appRole.AllowedMemberTypes.Add("User");
$appRole.DisplayName = $TenantName + " Users"
$appRole.Id = New-Guid
$appRole.IsEnabled = $true
$appRole.Description = "Users of the tenant"
$appRole.Value = $TenantName
$app.AppRoles.Add($appRole)
Set-AzureADApplication -ObjectId $ApplicationId -AppRoles $app.AppRoles
Reading the application works fine, when I print the app variable I can see it's the correct application. Executing the script from my own machine also gives no errors. Yet executing it via the runbook gives me:
Set-AzureADApplication : Error occurred while executing SetApplication
Code: Authorization_RequestDenied
Message: Insufficient privileges to complete the operation.
HttpStatusCode: Forbidden
HttpStatusDescription: Forbidden
HttpResponseStatus: Completed
By now I have given the automation application registration in Azure AD all rights of the Active Directory API. I have also clicked "Grant Permissions". I know it's the correct app registration because the script also invites an external user, when I gave the correct rights on the "Graph Api" that started to work.

I tried out your exact script in a run book and to make it work, I had to add code to "Login as the service principal" just before your PowerShell script. You can see more details here:
Using Azure Run As Account in Azure Automation
On the permissions front, I only gave 1 application permission (i.e. "Read and write all applications") and then clicked "Grant Permissions" as it did need Admin consent. Steps were done by a user with "Global administrator" directory role in my Azure AD.
Here is my final working PowerShell script (copied from edit runbook):
# Get Azure Run As Connection Name
$connectionName = "AzureRunAsConnection"
# Get the Service Principal connection details for the Connection name
$servicePrincipalConnection = Get-AutomationConnection -Name $connectionName
# Logging in to Azure AD with Service Principal
"Logging in to Azure AD..."
Connect-AzureAD -TenantId $servicePrincipalConnection.TenantId `
-ApplicationId $servicePrincipalConnection.ApplicationId `
-CertificateThumbprint $servicePrincipalConnection.CertificateThumbprint
$ApplicationId = "redacted-xxxx-xxxx-xxxx-xxxxxxxe3"
$TenantName = "RohitTenant"
$app = Get-AzureADApplication -ObjectId $ApplicationId
$appRole = New-Object Microsoft.Open.AzureAD.Model.AppRole
$appRole.AllowedMemberTypes = New-Object System.Collections.Generic.List[string]
$appRole.AllowedMemberTypes.Add("User");
$appRole.DisplayName = $TenantName + " Users"
$appRole.Id = New-Guid
$appRole.IsEnabled = $true
$appRole.Description = "Users of the tenant"
$appRole.Value = $TenantName
$app.AppRoles.Add($appRole)
Set-AzureADApplication -ObjectId $ApplicationId -AppRoles $app.AppRoles
Here are screenshots from some other important steps that I followed, which you may or may not have done already.
Create Azure Run As account while creating automation account
Make sure account settings for your automation account has the run as accounts now.
Find the App Registration created for Run as Account and give it permission to read and write all Azure AD applications.

Related

Run Get-AzADApplication from an Azure Runbook using a Managed Identity

Hi I am wanting to get App registration information for several applications within my tenant (but different subscriptions) using an Automation Account Runbook. I currently have a System Assigned managed identity on the automation account. Inside my powreshell workflow runbook I have the following snippet of code:
try{
"Logging in to Azure..."
#Connect-AzAccount
Connect-AzAccount -Identity
$token = (Get-AzAccessToken -ResourceTypeName MSGraph).token
$secreToken = ConvertTo-SecureString -String $token -AsPlainText -Force
Connect-MgGraph -AccessToken $secreToken
}
catch{
Write-Error -Message $_.Exception
throw $_.Exception
}
$apps = Get-AzADApplication
When I run the runbook I get an Insufficient privileges to complete the operation. error message. Is there anyway to run Get-AzADApplication using a managed identity in this way? If not, is there a better authentication method to use?
Firstly, I have tried your code and got similar error as you got as below:
Now, Firstly go to azure active directory and then click on Roles and administrators:
Then search directory Readers as below and then click on it:
Then click on add Assignments as below:
Then select a member as below:
then click on next:
Now then I go back to runbook and when I run the error is resolved:

Azure Automation Invoke-Sqlcmd : Login failed for user '<token-identified principal>'

I want to run an automation runbook to create a new database for each user and add them as a db_owner on their databases, but after I run the script I got this error "Invoke-Sqlcmd : Login failed for user '<token-identified principal>'. At line:94 char:14 + ... Invoke-Sqlcmd -ServerInstance $databaseServereInstance -D ... + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : InvalidOperation: (:) [Invoke-Sqlcmd], SqlException + FullyQualifiedErrorId : SqlExceptionError,Microsoft.SqlServer.Management.PowerShell.GetScriptCommand" for all users i tried to add as a db_owner.
Locally, the same code is working... only in Azure Automation runbook it's not working.
Below is my code.
$databaseServereInstance = '' This are filled i only removed it.
$sourceDatabaseName = ''
$azureRg = ''
$sourceServer = ''
# Login to Azure AD PowerShell With Admin Account
$connectionName = "AzureRunAsConnection"
$servicePrincipalConnection=Get-AutomationConnection -Name $connectionName
# Now you can login to Azure PowerShell with your Service Principal and Certificate
Connect-AzAccount -TenantId $servicePrincipalConnection.TenantId -ApplicationId $servicePrincipalConnection.ApplicationId -CertificateThumbprint $servicePrincipalConnection.CertificateThumbprint
Connect-AzureAD -TenantId $servicePrincipalConnection.TenantId -ApplicationId $servicePrincipalConnection.ApplicationId -CertificateThumbprint $servicePrincipalConnection.CertificateThumbprint
#Get-AzureADGroup
[String]$groupname = 'fiduciagad'
[Microsoft.Open.AzureAD.Model.Group]$getGroup = Get-AzureADGroup | Where { $PSItem.DisplayName -eq $groupname }
Write-Output $getGroup.DisplayName
if($getGroup -ne $null)
{
#Get-AzureADGroupMember
Get-AzureADGroupMember -ObjectId $getGroup.ObjectId | ForEach-Object -Process `
{
[String]$userPrincipalName = $PSItem.UserPrincipalName
Write-Output $userPrincipalName
[String]$givenName = $PSItem.GivenName
Write-Output $givenName
[String]$devUserPrincipalNameDatabase = "$($sourceDatabaseName)-DevDB-$($givenName)"
[String]$targetDatabase = (Get-AzSqlDatabase -Name "$devUserPrincipalNameDatabase" -ResourceGroupName $azureRg -ServerName $sourceServer).DatabaseName
#Remove source database for each user in a group
Remove-AzSqlDatabase -ResourceGroupName $azureRg -ServerName $sourceServer -DatabaseName $devUserPrincipalNameDatabase
#Copy source database for each user in a group and make the user db_owner
New-AzSqlDatabaseCopy -ResourceGroupName $azureRg -ServerName $sourceServer -DatabaseName $sourceDatabaseName `
-CopyResourceGroupName $azureRg -CopyServerName $sourceServer -CopyDatabaseName $devUserPrincipalNameDatabase
[String]$query = "
-- Add contained Azure AD user
CREATE USER [$($userPrincipalName)] FROM EXTERNAL PROVIDER WITH DEFAULT_SCHEMA = dbo;
-- Add user to role(s) in db
ALTER ROLE db_owner ADD MEMBER [$($userPrincipalName)];
"
[String]$getDbAzSqlAaccessToken = (Get-AzAccessToken -ResourceUrl 'https://database.windows.net/').Token
Write-Output $getDbAzSqlAaccessToken
Invoke-Sqlcmd -ServerInstance $databaseServereInstance -Database $devUserPrincipalNameDatabase -AccessToken $getDbAzSqlAaccessToken ` -query $query
}
}
else
{
Write-Verbose -Message "AzureAD Group could not be found..."
}
• Please ensure that the ServerInstance must be the full SQL Server name, not just the part that is declared as a variable. Since, locally, the server instance name might be picked up automatically since you are authenticating with first an AAD user.
• Also, check whether the AAD user through which you are signing into the database locally has the required permissions that your database needs. For example,
CREATE USER [<identity -name>] FROM EXTERNAL PROVIDER;
ALTER ROLE db_datareader ADD MEMBER [<identity-name>];
ALTER ROLE db_datawriter ADD MEMBER [<identity-name>];
ALTER ROLE db_ddladmin ADD MEMBER [<identity-name>];
• Here, < identity-name > is the name of the managed identity in Azure AD. If the identity is system-assigned, the name is always the same as the name of your App Service app. Also, the AAD user might not be added to the SQL DB that you are trying to connect or the AAD user through which you are running this script in Azure runtime doesn’t have AAD server admin role. You just need to add AAD user in Azure SQL DB.
Please find the below links for more information: -
https://learn.microsoft.com/en-us/azure/azure-sql/database/authentication-aad-configure?tabs=azure-powershell#create-contained-database-users-in-your-database-mapped-to-azure-ad-identities
https://learn.microsoft.com/en-us/answers/questions/133709/login-failed-for-user-39lttoken-identified-princip.html
Actually, I found a solution:
In Role and Assignments in Azure Active Directories, I added the role Directory Reader for AzureAutomationAccount and for the AzureSqlServer
After that, everything has been working.
Exactly, when you are trying to assign permissions to Azure AD identities (users, groups, service principals, managed identities) to an Azure SQL Database (or Azure Synapse-Serverless-SQL-Endpoint / Azure Synapse-Dedicated-SQL-Endpoint) you need to ensure that your service principal you are using to set the Azure AD permissions to your database has the following:
Read access to Azure AD (to verify the identities we would like to grant access to do exist in Azure AD)
=> IAM Role "Directory Reader" in Azure Active Directory
In Azure portal is set as:
"Azure Active Directory admin" (in Azure SQL DB)
"SQL Active Directory admin" (in Azure Synapse)
Connect to Azure SQL Database (where you want to grant access to) using "[Azure AD Authentication][1]".
So each user which should grant access to Azure AD identities require both of the just mentioned access rights.
To be able to grant access for Azure AD identities the authorizing user needs to use Azure AD authentication (step 2. opens the door for the first user on a fresh database without any Azure AD users authorized yet).
I also had the error "Invoke-Sqlcmd : Login failed for user '<token-identified principal>'". In my case the issue was a typo in the line of my Invoke-SqlCmd statement. If the parameter list is not correct or contains typos this error occurred. In the code example of the initial post
Invoke-Sqlcmd -ServerInstance $databaseServereInstance -Database $devUserPrincipalNameDatabase -AccessToken $getDbAzSqlAaccessToken -query $query`
I think the problem is the line break apostrophe character "****" before the command is completed (-query $query` is not in a new line, but even after the line break apostrophe. This caused confusion in my powershell script (using Azure DevOps Pipelines for CI).
My solution was to write the whole Invoke-SqlCmd command in one line without aprostrophes and without line breakes.
Invoke-Sqlcmd -ServerInstance $databaseServereInstance -Database $devUserPrincipalNameDatabase -AccessToken $getDbAzSqlAaccessToken -query $query

Programmatically authenticate into AAD with MFA via powershell

I have a PowerShell script that logs into Azure subscription with the command Connect-AzAccount using user's credentials.
The code is the following:
$userPassword='password'
$userName="username"
$tenantId="########-####-####-####-############"
$subscriptionId="########-####-####-####-############"
$azureSecpassword = $userPassword | ConvertTo-SecureString -asPlainText -Force
$azureCredential = New-Object System.Management.Automation.PSCredential($userName, $azureSecpassword)
Connect-AzAccount -Credential $azureCredential -Tenant $tenantId -SubscriptionId $subscriptionId
The code above works without any user interaction.
Few days ago the customer enabled the multi-factor authentication for the users.
How can I keep a fully automated login process (without user interactions) with the multi-factor authentication?
Best Regards.
This is a common question. Unfortunately, the answer is No. If the account is MFA-enabled, you could just login with an interactive way.
In such a case, we choose to use the service principal to login with non-interactive in general.
$azureAplicationId ="Azure AD Application Id"
$azureTenantId= "Your Tenant Id"
$azurePassword = ConvertTo-SecureString "client secret" -AsPlainText -Force
$psCred = New-Object System.Management.Automation.PSCredential($azureAplicationId , $azurePassword)
Connect-AzAccount -Credential $psCred -TenantId $azureTenantId -ServicePrincipal
Reference - Sign in with a service principal.
If you must log in as a user, there might be 2 optional approaches.
1. If you will run the script locally or in a specific PC
You can Persist Azure user credentials. You can enable auto save, or manually save the context to a file, and then use it in another PS session.
If you enabled auto save, then you can directly get the context as following:
Get-AzContext
# If you have more than one contexts, you can choose one by specifing the name
Get-AzContext -Name 'CSP Azure (e5b0****-****-****-****-5e5f****4c68) - jack#h****a.onmicrosoft.com'
If you want to manually do it, here is the sample:
# Interactively log for one time
Connect-AzAccount
# Save the context
Save-AzContext -Path D:\ctx.dat
And in another PS session, you can:
Import-AzContext -Path D:\ctx.dat
2. Use refresh token to acquire token, and connect to Azure
You can get the refresh token from the auto saved Azure context (usually at C:\Users\<UserName>\.Azure\TokenCache.dat).
Open the dat file with notepad, and you will get the refresh token:
Then you can get a new token in PowerShell with that refresh token, and connect to Azure:
Clear-AzContext
$tenantId = "e4c9ab4e-****-****-****-230b****57fb"
$subscriptionId = "e5b0fcfa-****-****-****-5e5f****4c68"
$refreshToken = 'AQABAAAAAAAP0****a lot of characters here*****0A9FWoB8mvDtoWRJHBVO7GJzodLKYmNIAA'
$url = "https://login.microsoftonline.com/" + $tenantId + "/oauth2/token"
$body = "grant_type=refresh_token&refresh_token=" + $refreshToken
$response = Invoke-RestMethod $url -Method POST -Body $body
$AccessToken = $response.access_token
Connect-AzAccount -AccountId "the user id, jack#h****a.onmicrosoft.com" -AccessToken $AccessToken -Tenant $tenantId -SubscriptionId $subscriptionId
How can I keep a fully automated login process (without user interactions) with the multi-factor authentication?
You can't do this with a user account--that's the whole point of multi-factor authentication.
Instead, Azure AD supports authenticating with a service principal (instead of a user principal, like you're doing currently), and Azure supports granting access to Azure resources to service principals.
MFA requirements (and other conditional access policies) do not apply to service principals (often referred to as an Azure AD "app"), and service principals support more secure methods of authentication for automation scenarios (e.g. public/private key pairs).
So, what you should do:
Ensure the machine running this script is secure. Anyone with access to the machine has the same amount of access as the script.
Create an application identity and associate credentials with it.
Note: It is strongly recommend you use certificate-based authentication for your service principal, instead of password-based. It is a very insecure practice to have any kind of secret stored in a PowerShell script!
Grant the service principal the minimum level of access to Azure resources, to allow it to complete the required task.
Update your script to use the app's identity (service principal) instead of the user's identity. It's even simpler than using a user account:
$tenantId = "########-####-####-####-############"
$subscriptionId = "########-####-####-####-############"
$appId = "########-####-####-####-############"
$thumbprint= "##############"
Connect-AzAccount -ServicePrincipal -TenantId $tenantId -ApplicationId $appId -CertificateThumbprint $thumbprint
Note: If this script is running on a VM in Azure, you should forget step 2, and simply enable a managed identity and use that.

How to connect-azaccount in Azure DevOps release pipeline

In the release pipeline, I am trying to connect to Azure AD by using Connect-Azaccount so I can run Get-AzADgroup to retrieve some Az AD group names and their guid and output to variables.
I created Azure Powershell task with the following inline script.
(Get-AzADGroup -DisplayName "group-name").origin
It seems you need to use a non-interactive login, follow the steps as below.
Create an Azure Active Directory application and create a secret for the app, save the secret and get values for signing in.
In your AD App -> API permissions -> Add a permission -> select Azure Active Directory Graph -> Application permissions -> Directory.Read.All -> click Add permissions -> click Grant admin consent for xxx, refer to the screenshot.
Try the script as below, use the values which you get in step 1, it works fine on my side.
Note: You need to use the Task version with 4.*(preview) when you use Az powershell module.
$azureAplicationId ="<your ad app application id>"
$azureTenantId= "<your tenant id>"
$azurePassword = ConvertTo-SecureString "<the secret of your ad app>" -AsPlainText -Force
$psCred = New-Object System.Management.Automation.PSCredential($azureAplicationId , $azurePassword)
Connect-AzAccount -Credential $psCred -TenantId $azureTenantId -ServicePrincipal
#I just test to get all groups, you could do other operations
Get-AzADGroup

Insufficient privileges assigning Azure Active Directory premissions to an MSI enabled Azure function?

We have two azure resources in the same directory. A webAPI set of APIs behind Azure API Management and an Azure Function. We want the azure function to be able to call the APIs. We've enabled MSI on the azure function as described in How to use managed identities for App Service and Azure Functions. We've created an App Registration in AAD for the API, created a role permission to be accessed. Following Calling your APIs with Azure AD Managed Service Identity using application permissions we run into errors attempting to assign the permission/role to the azure function:
in powershell:
New-AzureADServiceAppRoleAssignment -ObjectId 8XXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX -Id 3XXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX -PrincipalId 8XXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX -ResourceId 9XXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
New-AzureADServiceAppRoleAssignment : Error occurred while executing NewServicePrincipalAppRoleAssignment
Code: Authorization_RequestDenied
Message: Insufficient privileges to complete the operation.
HttpStatusCode: Forbidden
HttpStatusDescription: Forbidden
HttpResponseStatus: Completed
At line:1 char:1
+ New-AzureADServiceAppRoleAssignment -ObjectId 8XXXXXX-XXXX-XXXX-XXXX ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [New-AzureADServiceAppRoleAssignment], ApiException
+ FullyQualifiedErrorId : Microsoft.Open.AzureAD16.Client.ApiException,Microsoft.Open.AzureAD16.PowerShell.NewServ
icePrincipalAppRoleAssignment
is giving us a permission error, even when an AAD Admin (member of AAD DC Administrators I think) runs it. Has anyone run into this before? Why is this throwing a permissions error? We have verified that the ids are correct with 3 different people.
The problem you're probably facing is that, despite naming your app registration the same thing as your MSI-enabled app, the two end up representing different service principals in AAD. Using app registrations with MSI isn't currently supported.
Try running the powershell commands using the object id of the MSI identity instead. I was able to get this to work, and granted my MSI-enabled app access to the Graph Api.
Here is the PS I used to assign the GraphApi roles my function app required:
$functionAppName = "My-FANCY-FUNC"
$context = Get-AzureRmContext -ErrorAction SilentlyContinue #this lets you search AAD for func
if(!$context){
$login = Connect-AzureRmAccount | Out-Null
Connect-AzureAD #needed this for Graph API
$context = $login
} else { Write-Host "Login session already established for " $context.Subscription.SubscriptionName }
#get the SP associated with the MSI
$MSIPrincipal = Get-AzureRmADServicePrincipal -SearchString $functionAppName | Where-Object DisplayName -eq $functionAppName
#get the SP associatesd with the MS Graph
$graph = Get-AzureADServicePrincipal -All $true | ? { $_.DisplayName -match "Microsoft Graph" }
#find the target app roles in the graph
$targetRoles = $graph.AppRoles | Where-Object Value -in "Group.ReadWrite.All", "Directory.ReadWrite.All"
#iterate throgh the known roles and add the MSI SP to them
$targetRoles | ForEach-Object {New-AzureADServiceAppRoleAssignment -Id $_.Id -PrincipalId $MSIPrincipal.Id -ObjectId $MSIPrincipal.Id -ResourceId $graph.ObjectId}
I suspect, based on your question, that this line will return more than one entity:
Get-AzureRmADServicePrincipal -SearchString $functionAppName | Where-Object DisplayName -eq $functionAppName
Deleting your extraneous app registration should clear that up

Resources