Insufficient privileges to complete the operation - PowerShell Creating Azure Ad Apps fails - powershell

I have an Azure DevOps CI/CD wherein I have a PowerShell script to create a new Azure Ad App. Although the Service Principle has permissions granted against the following Graph APIs, yet, it fails with the following exception.
2020-12-14T11:49:55.0146669Z ##[error]Error occurred while executing GetApplications
Code: Authorization_RequestDenied
Message: Insufficient privileges to complete the operation.
RequestId: 1130fbec-3eec-4c8e-a2dd-e134f2c4621f
DateTimeStamp: Mon, 14 Dec 2020 11:49:54 GMT
HttpStatusCode: Forbidden
HttpStatusDescription: Forbidden
HttpResponseStatus: Completed
The API permissions that I have for the Service Principle are:
I use the following script to connect to Azure AD from the pipeline and create the Azure Ad App using PowerShell
Install-Module AzureAD -Force -Verbose -Scope CurrentUser
$context = [Microsoft.Azure.Commands.Common.Authentication.Abstractions.AzureRmProfileProvider]::Instance.Profile.DefaultContext
$graphToken = [Microsoft.Azure.Commands.Common.Authentication.AzureSession]::Instance.AuthenticationFactory.Authenticate($context.Account, $context.Environment, $context.Tenant.Id.ToString(), $null, [Microsoft.Azure.Commands.Common.Authentication.ShowDialog]::Never, $null, "https://graph.microsoft.com").AccessToken
$aadToken = [Microsoft.Azure.Commands.Common.Authentication.AzureSession]::Instance.AuthenticationFactory.Authenticate($context.Account, $context.Environment, $context.Tenant.Id.ToString(), $null, [Microsoft.Azure.Commands.Common.Authentication.ShowDialog]::Never, $null, "https://graph.windows.net").AccessToken
Connect-AzureAD -MsAccessToken $graphToken -AadAccessToken $aadToken -AccountId $context.Account.Id -TenantId $context.tenant.id
$ApiAppGuid = New-Guid
$ApiAppStartDate = Get-Date
$vvApiAppName = "dev0-vv-api"
# >>>>>> it must be failing here when trying to get the applications...
$appExists = ($vvApiApp = Get-AzureADApplication -Filter "DisplayName eq '$($vvApiAppName)'" -ErrorAction SilentlyContinue)
#... rest of the code that call New-AzureADApplication
Also, when I put the $graphTokenin jwt.io, it correctly shows the API permissions I've assigned to the Service Principle from Azure Portal.
I have no idea why it keeps failing despite the permissions consents granted! Any ideas highly appreciated - this is now almost taking a day.

As I suspected, Get-AzureADApplication uses Azure AD Graph API, and it is likely the given MS Graph API application permissions do not work there.
Some permissions do work in AAD Graph as well, but if it is a permission that was created later, it won't be supported.
So the solution is to use Get-AzureADMSApplication instead, which uses the MS Graph API.
Most scripts should be switched to use these any way since AAD Graph API is being deprecated.

Related

Connect-PnPOnline from Azure App and Runbook

we have an Azure App Registration and run a PowerShell-Script from a Azure RunBook to sync users from AAD to SharePoint User Profile Store.
The App has approved consent to read users by graph from aad and read/write to SharePoint User-Profiles:
The PowerShell-Script connects to graph and pnponline by App which is working fine.
$serviePrincipalName = 'ZZZ-SPOScript'
$servicePrincipalConnection=Get-AutomationConnection -Name $serviePrincipalName
Connect-MgGraph -TenantId $servicePrincipalConnection.TenantId -ClientId $servicePrincipalConnection.ApplicationID -CertificateThumbprint $servicePrincipalConnection.CertificateThumbprint
Connect-PnPOnline -Url "https://ourtenant-admin.sharepoint.com" -Tenant $servicePrincipalConnection.TenantId -ClientId $servicePrincipalConnection.ApplicationID -Thumbprint $servicePrincipalConnection.CertificateThumbprint
We can get all users from aad:
$users = Get-MgUser -All -Property "Id,mail,UserPrincipalName,extension_b8fc35d8e8ec45e689d332303177957a_ZZZ_townCode,extension_b8fc35d8e8ec45e689d332303177957a_ZZZ_costNumber,extension_b8fc35d8e8ec45e689d332303177957a_employeeID,extension_b8fc35d8e8ec45e689d332303177957a_employeeNumber,extension_b8fc35d8e8ec45e689d332303177957a_ZZZ_title,extension_b8fc35d8e8ec45e689d332303177957a_ZZZ_office,extension_b8fc35d8e8ec45e689d332303177957a_extensionAttribute6,extension_b8fc35d8e8ec45e689d332303177957a_extensionAttribute7"
We iterate over all users:
foreach($user in $users) {...
But when we try to get the user profile properties from SharePoint by calling
$fldValue = (Get-PnPUserProfileProperty -Account $user.UserPrincipalName).UserProfileProperties."ZZZ-CostNumber";
we get Current user is not a tenant administrator.
Our Service Principal ZZZ-SPOScript which runs the Script within the RunBook is of course not a tenant admin (and will never be).
So, we added an App Permission entry for the Azure App Registration by /_layouts/15/AppInv.aspx
with following Permissions to elevate to FullControl
<AppPermissionRequests>
<AppPermissionRequest Scope="http://sharepoint/content/tenant" Right="FullControl" />
</AppPermissionRequests>
But still we get Current user is not a tenant administrator. if the Scripts is trying to call
Get-PnPUserProfileProperty
Are we missing something or can this be a bug in pnponline?
Additional finding: As stated here we should add Full Control permissions for the social features.
So, I upated the permissions to
<AppPermissionRequests AllowAppOnlyPolicy="true">
<AppPermissionRequest Scope="http://sharepoint/content/tenant" Right="FullControl" />
<AppPermissionRequest Scope="http://sharepoint/social/tenant" Right="FullControl" />
But still same error.
Looks like there is simply a typo in the very first string:
$serviePrincipalName = 'ZZZ-SPOScript'
The correct one should be
$servicePrincipalName = 'ZZZ-SPOScript'

Execute an App registration without AzureAD

For a professional project, a chunk of the pipeline must be able to create an application (the first App registration, so I only have a global Admin) automatically within Azure AD. So far I used AzureAD which works well with Powershell 5.6 on Windows.
I now must be able to run the code with Ubuntu 20.04 and its Powershell 7.2. Unfortunately for me, AzureAD module is only supported on non-core Windows PowerShell, therefore it does not work on core PS6 or PS7. A very simplified piece of code is the following:
# Connection infos
$tenantId = "abcdef12345-1234-1234-124-abcdef12346789"
$account = "my_admin#domain.com" # Is cloud Admin by default
$password = ConvertTo-SecureString "MyPassword" -AsPlainText -Force
$psCred = New-Object System.Management.Automation.PSCredential -ArgumentList ($account, $password)
Connect-AzureAD -Credential $psCred -Tenant $tenantId
# Create app
$appName = "MyApp"
New-App -appName $appName -tenant_id $tenantId
I am stuck and my question is the following: how could I run such an operation with Powershell 7.2 considering AzureAD is not usable? I did check Connect-MgGraph for the connection part only (https://github.com/microsoftgraph/msgraph-sdk-powershell) but the clientId is an infos that I don't have -and want to create-.
Thanks in advance
You can use DeviceLogin as explained in this article to obtain an oAuth access token for you Global Administrator account in PowerShell (independent of the version) but this first step needs a human interaction.
After obtaining the token, you can use it to make Graph API calls with your Global Administrator permissions to create an application.
Once you create your first application, you must attribute required permissions and use it to automate the process (obtain token programmatically using API calls) for application creation in PowerShell.
You could use Resource Owner Password Credentials (ROPC) to authenticate, however Microsoft actively discourages it in their documentation due to the security implications of sending a password over the wire.
If the security issues present with this method of authentication are still tolerated within your acceptance criteria, you would still need a ClientID. Luckily, AzureAD has a well-known ClientID that you can use to authenticate. This ID is 1950a258-227b-4e31-a9cf-717495945fc2
The below Powershell code should get you started. I've basically translated the HTTP request within Microsoft's documentation into a splatted Invoke-RestMethod command.
$LoginWithROPCParameters = #{
URI = "https://login.microsoftonline.com/contoso.onmicrosoft.com/oauth2/v2.0/token"
Method = "POST"
Body = #{
client_id = "1950a258-227b-4e31-a9cf-717495945fc2"
scope = "user.read openid profile offline_access"
username = "username#contoso.onmicrosoft.com"
password = "hunter2"
grant_type = "password"
}
}
Invoke-RestMethod #LoginWithROPCParameters

Using Managed Identity in a Azure Function to access Graph API

Short version
I want to avoid username/passwords/secrets/key valults/etc. by using Managed Identity, in a PowerShell script, running on Azure Functions.
It seems to fail on:
Import-Module Microsoft.Graph.Authentication
Connect-MgGraph -Scopes 'Reports.Read.All', 'Group.ReadWrite.All'
Background
I have followed Access Microsoft Graph from a secured app as the app, and the setup seems to be fine.
I enable System Identity on the Azure Function and give permission to Microsoft Graph, when I check Enterprise App, permissions seems fine. And I find the managed identity in GraphAggregatorService (00000003-0000-0000-c000-000000000000).
When I debug from VScode, with my identity, the script works perfectly.
When Azure Functions runs the code, the following happens:
WARNING: Interactive authentication is not supported in this session,
falling back to DeviceCode. Future versions will not automatically fallback to DeviceCode.
Then
ERROR: Could not find file 'C:\home\site\wwwroot\.graph'.
Exception : Type : System.IO.FileNotFoundException
Message : Could not find file 'C:\home\site\wwwroot\.graph'.
FileName : C:\home\site\wwwroot\.graph TargetSite :
Name : MoveNext DeclaringType : Microsoft.Graph.PowerShell.Authentication.Cmdlets.ConnectMgGraph+<ProcessRecordAsync>d__52, Microsoft.Graph.Authentication, Version=1.6.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35
...<cut>
Line : Connect-MgGraph -Scopes 'Reports.Read.All', 'Group.ReadWrite.All' PositionMessage : At C:\home\site\wwwroot\TestPowerShellTimer\run.ps1:15 char:1 + Connect-MgGraph -Scopes 'Reports.Read.All', 'Group.ReadWrite.All'
...<cut>
Code
# Input bindings are passed in via param block.
param($Timer)
Import-Module Microsoft.Graph.Authentication
Connect-MgGraph -Scopes 'Reports.Read.All', 'Group.ReadWrite.All'
$reportJson = Invoke-GraphRequest -Uri 'https://graph.microsoft.com/beta/reports/credentialUserRegistrationDetails?$top=5000' -Method GET
Why
I could have used Connect-MsOnline (doing the same with msonline), but the code uses uses significantly longer time to complete.
What
I am looping through our users to see if they have set up MFA, and add them to a security group. No, there is no way to do this with azure ad dynamic groups afaik. But if someone has suggestions to other ways of achieving this, feel free to mention it...
My thoughts
Obviously, I am missing something or totally misunderstood something.
The part of the errormsg "C:\home\site\wwwroot.graph", I cannot figure out. Google is not my friend at this point.
I guess I have to, somehow, instruct AzF to use the managed identity:
Import-Module Microsoft.Graph.Authentication
Connect-MgGraph -Scopes -TenantId -ClientId
but I cannot figure out how. I realize that I have ended up with guessing, and then it is time to ask for help.
How to use managed identity in Azure Functions with the Microsoft.Graph.Authentication module?
Since you are using a system identity, which is already connected to Azure, you can generate an access token and pass it down to Connect-MGGraph -AccessToken
Here's a function I made about a year or two ago that serve that exact purpose.
(I needed any authenticated users to be able to gain a token to any azure endpoints based on their currently connected identity without having any other form of credential validation)
function Get-AzToken {
[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[String]
$ResourceUri,
[Switch]$AsHeader
)
$Context = [Microsoft.Azure.Commands.Common.Authentication.Abstractions.AzureRmProfileProvider]::Instance.Profile.DefaultContext
$Token = [Microsoft.Azure.Commands.Common.Authentication.AzureSession]::Instance.AuthenticationFactory.Authenticate($context.Account, $context.Environment, $context.Tenant.Id.ToString(), $null, [Microsoft.Azure.Commands.Common.Authentication.ShowDialog]::Never, $null, $ResourceUri).AccessToken
if ($AsHeader) {
return #{Headers = #{Authorization = "Bearer $Token" } }
}
return $Token
}
$Token = Get-AzToken -ResourceUri 'https://graph.microsoft.com/'
Connect-MgGraph -AccessToken $Token
Note that for this to work, in your Azure function profile.ps1, you should have the Connect-AzAccount -Identity line uncommented.
Profile.ps1 snippet
if ($env:MSI_SECRET) {
Disable-AzContextAutosave -Scope Process | Out-Null
Connect-AzAccount -Identity
}
This method will work to obtain token for any of the Azure endpoints, including your own functions, should you enable the Azure AD authentication.
Bonus
Here is how you would use it to call your Azure function if the Azure function is protected by Azure AD authentication.
# This work assuming you connecteded through Connect-AzAccount at some point.
# ResourceUri is the ClientID of your application.
$Headers = Get-AzToken -ResourceUri '3953d051-c61f-43c5-8848-487a921aae31' -AsHeader
$base = 'https://YourFunctionUrl.azurewebsites.net/api'
Invoke-RestMethod -Method Get -Uri "$base/MyApiEndpoint" #Headers
Here is a quicker way to get MS Graph token and Azure AD Graph token. It's 4 lines from below. I provide more to cover all use cases.
# use this line for system managed identity
Connect-AzAccount -Identity
# use this line for user managed identity, specify its AppID as AccountId
Connect-AzAccount -Identity -AccountId <ClientID>
$context = [Microsoft.Azure.Commands.Common.Authentication.Abstractions.AzureRmProfileProvider]::Instance.Profile.DefaultContext
$graphToken = [Microsoft.Azure.Commands.Common.Authentication.AzureSession]::Instance.AuthenticationFactory.Authenticate($context.Account, $context.Environment, $context.Tenant.Id.ToString(), $null, [Microsoft.Azure.Commands.Common.Authentication.ShowDialog]::Never, $null, "https://graph.microsoft.com").AccessToken
$aadToken = [Microsoft.Azure.Commands.Common.Authentication.AzureSession]::Instance.AuthenticationFactory.Authenticate($context.Account, $context.Environment, $context.Tenant.Id.ToString(), $null, [Microsoft.Azure.Commands.Common.Authentication.ShowDialog]::Never, $null, "https://graph.windows.net").AccessToken
Write-Output "Hi I'm $($context.Account.Id)"
# Connect to AAD to use Azure AD Graph
Connect-AzureAD -AadAccessToken $aadToken -AccountId $context.Account.Id -TenantId $context.tenant.id
# To use MS Graph use the below line
Connect-MgGraph -AccessToken $graphToken
Note that Azure AD Graph is retiring on 30 June 2022 and is not recommended anymore. Accordingly you should not use AzureAD commandlets which are based on this SDK.

Azure AD authentication via Powershell returns null

I'm trying to authenticate to my Intune tenant using Powershell & the AzureAD module. In Windows Powershell (5.1) it works, but in Powershell 7 the same code returns null. I get the login prompt, enter my credentials, and respond to the MFA prompt on my phone. Any ideas on what's happening?
$Resource = "https://graph.microsoft.com"
$ClientID = "d1ddf0e4-d672-4dae-b554-9d5bdfd93547"
$RedirectUri = "urn:ietf:wg:oauth:2.0:oob"
# $PlatformParams has PromptBehavior set to Always
$Authority = "https://login.microsoftonline.com/mytenant.onmicrosoft.com/oauth2/v2.0/token"
$AuthenticationContext = New-Object -TypeName "Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext" -ArgumentList $Authority -ErrorAction Stop
$AuthenticationResult = ($AuthenticationContext.AcquireTokenAsync($Resource, $ClientID, $RedirectUri, $PlatformParams)).Result
$AuthenticationResult is null in Powershell 7, but contains the expected data in Powershell 5.1.
Powershell 6/7 (aka cross-platform core) encourages you to use new Az modules based on CLI. For AAD, refer this and get access token reference.
If you still insist to use the legacy AzureAD module for some reason, consider running in compatibility mode (though I don't see reason to stick to the legacy once since you already seem to got into the new powershell). People have written blog post about that (Disclaimer: Haven't tried myself).
Also in your example code, I see you are anyway not using any powershell cmdlet, but ADAL objects. That is another thing I recommend you to avoid (though not directly related to this problem). ADAL is deprecated and replaced by MSAL. Powershell module MSAL.PS.
Example using MSAL.PS:
Install-Module -Name MSAL.PS
Get-MsalToken -ClientId 'd1ddf0e4-d672-4dae-b554-9d5bdfd93547' -TenantId 'mytenant.onmicrosoft.com' -Scopes 'https://graph.microsoft.com/.default'

Using Powershell to get Azure AD Token (jwt)

I am trying to get a jwt token from AAD using Powershell using Username/Password authentication.
I am writing a powershell script that will to call an API using a bearer token. What I have works if I copy & paste the token from an SPA that uses the API. I am looking for a way to retrieve the token from my powershell.
This looks really promising: https://github.com/Azure-Samples/active-directory-dotnet-native-headless/blob/master/TodoListClient/Program.cs
I feel like I'm smacking my head against a wall trying to create a 'UserPasswordCredential' object. Any clues to how I can do this would be super-helpful.
I have Add-Type-ed:
- Microsoft.IdentityModel.Clients.ActiveDirectory.dll
- Microsoft.IdentityModel.Clients.ActiveDirectory.platform.dll (adds nothing?)
- Microsoft.IdentityModel.Clients.ActiveDirectory.WindowsForms.dll
- Microsoft.IdentityModel.Clients.ActiveDirectory.WindowsForms.dll
The docs page for 'UserPasswordCredential' :
https://learn.microsoft.com/en-us/dotnet/api/microsoft.identitymodel.clients.activedirectory.userpasswordcredential
It should be in one of the first two dlls
This, under 'Constraints & Limitations', makes me think it may not actually be possible from powershell:
http://www.cloudidentity.com/blog/2014/07/08/using-adal-net-to-authenticate-users-via-usernamepassword/
Looking at the code below, the first acquire token succeeds, the second fails - possibly/probably because $cred is a UserCredential not a UserPasswordCredential.
Is is possible to do this with powershell?
Finally, on a totally different track, how do I find the values for redirectUri and resourceAppIdURI that my application needs? When I look in the AAD console, and browser to my Enterprise Application, I can find the AppId (which I can use as $clientId).
I'm not sure the redirectUri is strictly necessary for me as all I really want is the token, but I can have a good guess at what it should be.
When I try to call the first AquireToken method (without $cred) using my app details, it fails with this message:
Exception calling "AcquireToken" with "4" argument(s): "AADSTS50001: The application named https://myappwithapi/Login was not found in the tenant named me.onmicrosoft.com.
Is it possible for me to find the require value for resourceAppIdURI by looking in my azure portal?
'https://myappwithapi/Login' is from my azure portal > enterprise apps > [app' > properties > HomepageUrl
code:
#setup
$TenantName = "mme.onmicrosoft.com"
$clientId = "1950a258-227b-4e31-a9cf-717495945fc2" # Microsoft
$clientId = "03faf8db-..........................." #
$username = "me#me.onmicrosoft.com"
$password = Read-Host -AsSecureString -Prompt "Enter Password"
# add dlls
$adal = "${env:ProgramFiles(x86)}\Microsoft SDKs\Azure\PowerShell\ServiceManagement\Azure\Services\Microsoft.IdentityModel.Clients.ActiveDirectory.dll"
$adalforms = "${env:ProgramFiles(x86)}\Microsoft SDKs\Azure\PowerShell\ServiceManagement\Azure\Services\Microsoft.IdentityModel.Clients.ActiveDirectory.WindowsForms.dll"
$adalplatform = "${env:ProgramFiles(x86)}\Microsoft SDKs\Azure\PowerShell\ServiceManagement\Azure\Services\Microsoft.IdentityModel.Clients.ActiveDirectory.platform.dll"
[System.Reflection.Assembly]::LoadFrom($adal) | Out-Null
[System.Reflection.Assembly]::LoadFrom($adalforms) | Out-Null
[System.Reflection.Assembly]::LoadFrom($adalplatform) | Out-Null
#prep request
$redirectUri = "urn:ietf:wg:oauth:2.0:oob" # Microsoft
$resourceAppIdURI = "https://graph.windows.net"
$authority = "https://login.windows.net/$TenantName"
$authContext = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext" -ArgumentList $authority
# Get Token prompting for creds
$authResult = $authContext.AcquireToken($resourceAppIdURI, $clientId, $redirectUri, "Always")
$authResult
# Get the cred
$cred = New-Object -TypeName 'Microsoft.IdentityModel.Clients.ActiveDirectory.UserCredential' -ArgumentList $username, $password
#$cred = New-Object -TypeName 'Microsoft.IdentityModel.Clients.ActiveDirectory.UserPassCredential' -ArgumentList $username, $password
$authResult = $authContext.AcquireToken($resourceAppIdURI, $clientId, $cred)
$authResult
This post has more the one question in it.
Your base use case 'Using Powershell to get Azure AD Token (jwt)' is a common one and there are several samples and pre-built examples to leverage. For example:
https://github.com/pcgeek86/AzureADToken
A PowerShell module that allows you to get a JSON Web Token (JWT) from Azure Active Directory (AAD).
https://gallery.technet.microsoft.com/Get-Azure-AD-Bearer-Token-37f3be03
This script acquires a bearer token that can be used to authenticate to the Azure Resource Manager API with tools such as Postman. It uses the Active Directory Authentication Library that is installed with the Azure SDK.
See if those two resources resolves your use base line use case.
As for this...
"Is it possible for me to find the require value for resourceAppIdURI by looking in my azure portal?"
You can do this via a remote PowerShell logon to AzureAD. Install the AAD PowerShell module.
https://learn.microsoft.com/en-us/powershell/azure/overview?view=azurermps-5.1.1
https://msdn.microsoft.com/en-us/library/dn135248(v=nav.70).aspx
Download and install MSOL. Sign in with the MSOL
https://www.microsoft.com/en-US/download/details.aspx?id=39267
The Microsoft Online Services Sign-In Assistant provides end user sign-in capabilities
and use the built-in cmdlets to pull your information from your organization settings, and or hit the MSGraph API and query.
https://learn.microsoft.com/en-us/powershell/azure/active-directory/install-adv2?view=azureadps-2.0
You can use the Azure Active Directory PowerShell Module Version for Graph for Azure AD administrative tasks
As for this one:
"how do I find the values for redirectUri and resourceAppIdURI that my application needs?"
This is in your app registration section of your portal. The developer team provide the redir uri not Azure. It's part of the registration process all else is generated by Azure App Reg process.
The app registration process is here and of course you are someone else had to register this app in AzureAD, and thus can retrieve it at any time.:
https://blogs.msdn.microsoft.com/onenotedev/2015/04/30/register-your-application-in-azure-ad
Any registered apps and their details can be retrieved using...
Get-AzureADApplication
Get-AzureADApplication | Select -Property *
(Get-AzureADApplication).ReplyUrls
Get-AzureADApplication | Select -Property AppID, DisplayName,ReplyUrls
https://learn.microsoft.com/en-us/powershell/module/azuread/get-azureadapplication?view=azureadps-2.0