Access Azure PIM api in azure pipelines via service principal - azure

I'm trying to call the azure privileged identity management api (https://api.azrbac.mspim.azure.com/api/v2/privilegedAccess) in an azure pipeline. I have the following code to call the register method, but it is not working, but I can't figure out what is wrong. Let me show the code first:
install-module azureadpreview -Force
import-module azureadpreview
$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
$pimToken = [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, "<what do i enter here?>").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 -AadAccessToken $aadToken -AccountId $context.Account.Id -TenantId $context.tenant.id -MsAccessToken $graphToken
Write-Output "Create PIM role"
$Group = New-AzureADMSGroup -DisplayName "TestPIMGroup" -Description "TestForPim" -MailEnabled $false -SecurityEnabled $true -MailNickName "NOTUSED" -IsAssignableToRole $true
Write-Output "Test api call"
$Headers = #{
"Accept" = "*/*"
"Accept-Language" = "en"
"Authorization" = "Bearer {0}" -f $pimToken
"Content-Type" = "application/json"
}
$Body = #{
externalId = $Group.Id
} | ConvertTo-Json
$URL = 'https://api.azrbac.mspim.azure.com/api/v2/privilegedAccess/aadGroups/resources/register'
Write-Output "Body: $Body"
$HeaderJson = $Headers | ConvertTo-Json
Write-Output "Headers: $HeaderJson"
try {
$QueryResponse = Invoke-RestMethod -Uri $URL -Headers $Headers -Method POST -Body $Body
}
catch {
$_.Exception.Response
$result = $_.Exception.Response.GetResponseStream()
$reader = New-Object System.IO.StreamReader($result)
$reader.BaseStream.Position = 0
$reader.DiscardBufferedData()
$responseBody = $reader.ReadToEnd();
$responseBody
exit 1
}
$QueryResponse.value
So what I'm trying to accomplish, it to create a PIM group, and enable privileged access on it. The azureadpreview module has functionality to create a PIM group, so I use that. This works perfectly. The method of getting the token for the service principal I got from this post.
Now to enable privileged access on it, I need to directly call the API, because there doesn't seem to be any powershell command for it. This where things get tricky. The API call returns a 500 internal server error, with the only error in the body being an error has occurred. So that doesn't really tell me anything. So I started investigating:
When I don't pass a token, I get an unauthorized exception
When I pass nonsense as a token, I get an internal server error. So this pointed me in the direction that something is wrong with the token. I think there is something wrong with the audience tag in the token.
I tried all kinds of URL's in the token, which all show me a 500 as response.
I recorded the api call the browser did in azure portal when doing the same call. I read the token from this request, and it got me some guid as aud. When I use this guid, it given me a Unauthorized response
Can anyone help me with my situation?
answer restriction:
I need to be able to access the pim api by reusing the service principal I'm already using in my azure pipeline via my service connection. So I'm not looking for answers about creating certificates in azure an authenticating using it.
Edit:
Some background info:
https://feedback.azure.com/forums/169401-azure-active-directory/suggestions/42203638-allow-privileged-access-for-groups-to-be-enabled-f
https://feedback.azure.com/forums/34192--general-feedback/suggestions/42644962-possibility-for-azure-ad-privileged-access-groups
https://github.com/MicrosoftDocs/azure-docs/issues/70296#issuecomment-776069283

The guid you get from token in browser in azure portal should be correct.
But this API endpoint looks like to be not exposed for external usage.
We can not generate an application token for this resource currently.
As you see there are several user voice posts are requiring this feature.
Keep voting up the posts will be helpful.

Related

Invoke-RestMethod :unauthorized client for getting Authentication-token

I have created app registration in azure active directory. I'm trying to invoke an azure ad authenticated with below PowerShell script, but it always display an error:
$clientID = '<clientID>'
$secretKey = '<key>'
$tenantID = '<TenantID>'
$password = ConvertTo-SecureString -String $secretKey -AsPlainText -Force
$credential = New-Object System.Management.Automation.PSCredential($ClientID,$password)
Connect-AzureRmAccount -ServicePrincipal -Credential $credential -Tenant $tenantID
$authUrl = "https://login.microsoftonline.com/" + $tenantID + "/oauth2/v2.0/token/"
$body = #{
"scope" = "api://a193b314b-7854-9aab-bb78-6a50ffxxxxxx/";
"grant_type" = "client_credentials";
"client_id" = $ClientID
"client_secret" = $secretKey
}
Write-Output "Getting Authentication-Token"
$adlsToken = Invoke-RestMethod -Uri $authUrl –Method POST -Body $body
Write-Output $adlsToken
I am getting this error. please make me to understand Why I am getting this error
Invoke-RestMethod:
{"error":"invalid_scope","error_description":"AADSTS1002012: The provided value for scope api://3e3643c5-90af-ece is not valid. Client credential flows must have a scope value with /.default suffixed to the resource identifier (application ID URI).\r\nTrace ID:2d4f23bf-b317-4d5c-b5xxxxx\r\nCorrelation ID:fe5945b4-b2c2-4814-9xxxxxxx\r\nTimestamp:04:26:09Z","error_codes":[1002012],"timestamp":"2022-11-19
04:26:09Z","trace_id":"2d4f23bfb3174d5cb5a7xxxxxxx","correlation_id":"fe5945b4-b2c2-4814-99xxxxxxxx"}
Connect-AzAccount: ClientSecretCredential authentication failed:
AADSTS700016: Application with identifier
'3e3643c5-90af-4af6-afxxxxxxx' was not found in the directory 'Default
I tried to reproduce the same in my environment I got the same error as below:
To resolve this issue, check whether you are providing correct ClientID as below:
And, In scope the error mention you have missed /.default Make sure to include /.default like below:
"api://xxxxxx/.default";
When I ran the same script along with scope default, I got the Result successfully like below:
Can you try this by adding .default in scope,
"scope" = "api://a193b314b-7854-9aab-bb78-6a50ffxxxxxx/.default"
If it works, see the reference.

Creating Azure Runbook using a System assigned identity to make a GRAPH Request

Currently I am authenticating the system management identity using the resources found here
Runbook Authentication Tutorial
In the tutorial I am using this workflow to connect to my System management identity
# Resources
# https://learn.microsoft.com/en-us/azure/automation/learn/automation-tutorial-runbook-textual
# https://learn.microsoft.com/en-us/azure/app-service/overview-managed-identity?tabs=portal%2Cpowershell
param(
[String] $resourceGroup = "ResourceGroupName", # Resource Group
[String] $subscription = "SubscriptionName", # Subscription name
[String] $SAMI = "Default tenant ID" # System Access Management Identity (Tenant ID)
)
$automationAccount = "myAutomationAccountName"
Disable-AzContextAutosave -Scope Process | out-null
try {
$AzureContext = (Connect-AzAccount -Identity).context
$azProfile = [Microsoft.Azure.Commands.Common.Authentication.Abstractions.AzureRmProfileProvider]::Instance.Profile
$profileClient = New-Object -TypeName Microsoft.Azure.Commands.ResourceManager.Common.RMProfileClient -ArgumentList ($azProfile)
$token = $profileClient.AcquireAccessToken($AzureContext.Subscription.TenantId)
if(-not ($token.AccessToken)){
throw
}
$authHeader = #{
'Content-Type'='application/json'
'Authorization'='Bearer ' + $token.AccessToken
'ExpiresOn' = $token.ExpiresOn
'X-IDENTITY-HEADER'= $env:IDENTITY_HEADER
}
# Output the generated access token
Write-Output $token.AccessToken
# I want to make a graph request getting a list of devices and then use that to set the last logged-on user as the primary user. I can't even make a request though...
$uri = "https://graph.microsoft.com/v1.0/deviceManagement/managedDevices"
Invoke-RestMethod -Uri $uri -Headers $authHeader -Method Get
} catch{
write-output $_.Exception.Message
}
$AzureContext = Set-AzContext -SubscriptionName $subscription -DefaultProfile $AzureContext
When I start the runbook in the Testpane it outputs the JWT. Then, when trying to make the request it throws an error
The remote server returned an error: (401) Unauthorized.
I then open PostMan and enter the URL (endpoint) where I know a valid JWT created from my user account would work, I place this generated token in the Authorization Bearer token and receive this JSON error
{
"error": {
"code": "InvalidAuthenticationToken",
"message": "Access token validation failure. Invalid audience.",
"innerError": {
"date": "2022-08-22T19:14:43",
"request-id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx",
"client-request-id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx"
}
}
}
I go to https://jwt.io/ and get the encoded JWT, and notice that the expiration date is exactly when it was created. Is this a possible problem? If so how can I extend the expiration date within this runbook? or else, is it a role-based issue with GRAPH API rejecting the token? Please help...
I have followed MSDOC I can be able to view the Graph request in a write-output.
Make sure specific Read access Role assigned to the specific user.
Workaround
I have made a request in an Azure Runbook to catch the response of a Graph request.
Result

Using token from Connect-AZAccount on Intune Graph calls

So, after years consuming valuable information from StackOverflow this is my first question!
I used part of a code I found when researching as I start to move away from App Secrets in Graph API and prepare to use Managed Identities.
It successfully authenticate and generate the token when running locally(haven't tested anything on Azure Automation yet as this would be the PoC) but it will not work for everything in Graph, it works when querying AD Users and Groups but not on anything else, it seems it is not getting all the scopes the accounts has access to although the account is global admin.
This part will check if you are using Managed Identity or prompt for login
#Requires -Modules #{ ModuleName="Az.Accounts"; ModuleVersion="2.7.0" } , #{ ModuleName="Az.Resources"; ModuleVersion="5.1.0" }
Param(
[Switch]$nonInteractive
)
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Web")
$res = [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls -bor [Net.SecurityProtocolType]::Tls11 -bor [Net.SecurityProtocolType]::Tls12
if(!$Global:AZConnection){
try{
if($nonInteractive){
Write-Output "Logging in with MI"
$Null = Connect-AzAccount -Identity -ErrorAction Stop
Write-Output "Logged in as MI"
}else{
Write-Output "Logging in to Azure AD"
$Global:AZConnection = Connect-AzAccount -Force -ErrorAction Stop
Write-Output "Logged in to Azure AD with $($Global:AZConnection.Context.Account.id)"
}
}catch{
Throw $_
}
}
After this it will get the context and authenticate against graph.microsoft.com & graph.windows.net to get both tokens.
It will also prepare the headers to be used on WebRequests
$context = [Microsoft.Azure.Commands.Common.Authentication.Abstractions.AzureRmProfileProvider]::Instance.Profile.DefaultContext
$graph = ([Microsoft.Azure.Commands.Common.Authentication.AzureSession]::Instance.AuthenticationFactory.Authenticate($context.Account, $context.Environment, $context.Tenant.Id.ToString(), $null, [Microsoft.Azure.Commands.Common.Authentication.ShowDialog]::Always, $null, "https://graph.microsoft.com"))
$graphToken = $graph.AccessToken
$AAD = ([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"))
$aadToken = $AAD.AccessToken
$HeadersGraph = #{
'Content-Type' = "application\json"
'Authorization' = "Bearer $graphToken"
}
$HeadersAAD = #{
'Content-Type' = "application\json"
'Authorization' = "Bearer $aadToken"
}
Trying to use the token to query user or group information will work
$UserData = Invoke-RestMethod -Uri "https://graph.microsoft.com/beta/Users" -Method GET -Headers $HeadersGraph
$GroupData = Invoke-RestMethod -Uri "https://graph.microsoft.com/beta/Users" -Method GET -Headers $HeadersGraph
But if I try to query any Intune or SharePoint URI it will give me:
Invoke-RestMethod : The remote server returned an error: (403) Forbidden.
$deviceData = Invoke-RestMethod -Uri "https://graph.microsoft.com/beta/deviceManagement/managedDevices" -Method GET -Headers $HeadersGraph
$SharepointData = Invoke-RestMethod -Uri "https://graph.microsoft.com/beta/sites" -Method GET -Headers $HeadersGraph
I can use these Tokens to authenticate on other modules but I was trying to avoid it and keep everything on Graph web requests
#This also works
$Intune = Connect-MgGraph -AccessToken $graphToken
$AzureAD = Connect-AzureAD -AadAccessToken $aadToken -AccountId $context.Account.Id
Does anyone know of any resource principal that I can authenticate that would give me a valid token to make calls to SharePoint and Intune?
I assume that you're aware that this is a permission issue and not a token issue.
I have no solution for your problem. I just want to let you know, that nowadays there is an easier way to get Graph Access tokens (since you are already using the Az.Accounts module).
https://learn.microsoft.com/en-us/powershell/module/az.accounts/get-azaccesstoken

I would like to give admin consent to API permissions through powershell script

I have a code in Az module of powershell to create appID, app secret and assign API permission. How do I grant admin consent to all the API permissions that I assigned to the AzApp?
...
$context = Get-AzContext
$ResourceAppIdURI = "https://graph.windows.net/"
$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, $ResourceAppIdURI).AccessToken
$headers = #{ }
$headers.Add("Content-Type", "application/json")
$headers.Add("Accept", "application/json")
$headers.Add("Authorization", "Bearer $($token)")
$objectID = $myApp.ObjectId
$url = "https://graph.windows.net/$tenant/applications/{0}?api-version=1.6" -f $objectID
Write-Host "URL: " $url
$postData = "{`"requiredResourceAccess`":[
{`"resourceAppId`":`"00000003-0000-0000-c000-000000000000`",
`"resourceAccess`":[
{`"id`":`"e1fe6dd8-ba31-4d61-89e7-88639da4683d`",`"type`":`"Scope`"},
{`"id`":`"7ab1d382-f21e-4acd-a863-ba3e13f7da61`",`"type`":`"Role`"},
{`"id`":`"5b567255-7703-4780-807c-7be8301ae99b`",`"type`":`"Role`"},
{`"id`":`"e2a3a72e-5f79-4c64-b1b1-878b674786c9`",`"type`":`"Role`"},
{`"id`":`"df021288-bdef-4463-88db-98f22de89214`",`"type`":`"Role`"}
]
}]
}";
Invoke-RestMethod -Uri $url -Method "PATCH" -Headers $headers -Body $postData
Write-Host "App created..."
Write-Host "AppID: " $myApp.ApplicationId
Write-Host "App Secret: " $secret
Write-Host "TenantID: " $tenant.Id
There is no API exposed by Microsoft to grant admin consent for Azure AD application / service principal. You can vote this post on User Voice.
There is a workaround:
Call Microsoft Graph API Create a delegated permission grant and Grant an appRoleAssignment to a service principal in Powershell.
A sample for your reference:
$context = Get-AzContext
$ResourceAppIdURI = "https://graph.windows.net/"
$ResourceGraphURI = "https://graph.microsoft.com/"
$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, $ResourceAppIdURI).AccessToken
$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, $ResourceGraphURI).AccessToken
$clientID = "d154cc56-f1a2-4906-9f26-bfb4756f9c20"
$resourceID = "08a1faff-51c1-4cbb-81c4-1bc11286da76"
$scopes = "Sites.Read.All User.Read User.Read.All User.ReadBasic.All"
$body = #{
clientId = $clientID
consentType = "AllPrincipals"
principalId = $null
resourceId = $resourceID
scope = $scopes
startTime = "2019-10-19T10:37:00Z"
expiryTime = "2020-10-19T10:37:00Z"
}
$apiUrl = "https://graph.microsoft.com/beta/oauth2PermissionGrants"
Invoke-RestMethod -Uri $apiUrl -Headers #{Authorization = "Bearer $($graphToken)" } -Method POST -Body $($body | convertto-json) -ContentType "application/json"
$principalId = "d154cc56-f1a2-4906-9f26-bfb4756f9c20"
$body1 = #{
principalId = $principalId
resourceId = $resourceID
appRoleId = "df021288-bdef-4463-88db-98f22de89214"
}
$apiUrl1 = "https://graph.microsoft.com/beta/servicePrincipals/$($principalId)/appRoleAssignedTo"
Invoke-RestMethod -Uri $apiUrl1 -Headers #{Authorization = "Bearer $($graphToken)" } -Method POST -Body $($body1 | convertto-json) -ContentType "application/json"
For the first call https://graph.microsoft.com/beta/oauth2PermissionGrants:
clientID is the object id of the service principal (not Azure AD application), you can find it using Get-AzADServicePrincipal. You can also find it on Azure Portal - Azure Active Directory - Enterprise Applications, search for the name of your Azure AD application.
resouceID is the object id of Microsoft Graph service principal. You can find under Enterprise applications (search for "00000003-0000-0000-c000-000000000000").
scopes are the delegated permissions you want to grant admin consent.
For the second call https://graph.microsoft.com/beta/servicePrincipals/$($principalId)/appRoleAssignedTo:
principalId is the same as clientID mentioned above.
appRoleId is the application permission id.
Actually, the Azure AD PowerShell module provides a cmdlet equivalent for Application Permissions : New-AzureADServiceAppRoleAssignment.
Even if it's poorly documented, the command adds the requested application permissions (and grant admin consent if you have the right to do so) to your AAD Application (through the service principal).
# If it's not the case, declare your AAD Application as a service principal (Enterprise Application)
$aadappsp = New-AzureADServicePrincipal -AppId "AAD_APPLICATION_ID"
# Id of the application permission (role)
$roleId = "2a8d57a5-4090-4a41-bf1c-3c621d2ccad3" # TermStore.Read.All
# Object Id of the concerned Service Principal (could be Graph or SharePoint for example)
# (Not the Application Id like "00000003-0000-0ff1-ce00-000000000000" for SharePoint)
$aadSpObjectId = "c30e8a24-ff90-464e-aed3-7c39a7bdc280"
# Register the application permission
New-AzureADServiceAppRoleAssignment -ObjectId $aadappsp.ObjectId -Id $roleId -PrincipalId $aadappsp.ObjectId -ResourceId $aadSpObjectId
It's using a dedicated endpoint, so don't be surprised if you have this display once the command correctly executed:
(permissions added through PowerShell appear as "Other permissions granted for...")
To avoid that, you have to first add them through interface or with New-AzureADApplication (to register the permissions as "configured") and New-AzureADServicePrincipal (to grant admin consent properly for your organization).
Sadly, there's no cmdlet for granting admin consent on Delegated Permissions, so the answer provided by #Allen Wu still works in this case (just update the URIs to use v1.0 version instead of beta).

How to get the access token to Azure API Management programmatically?

I'm trying to implement Azure Active Directory in my API Management instance using the Protect an API by using OAuth 2.0 with Azure Active Directory and API Management doc. The doc suggests that in order to get the access token I need to use the Developer Portal.
My problem is: An external application is going to communicate with API Management. Is there a way to omit the Developer Portal and get the access token programmatically?
It's a pain but thanks to Jos Lieben I am able to do it with this Powershell function
It's specifically for granting API access on behalf of the Org, but as you can see you can extract the commands to get and use the API token.
Original Author Link: https://www.lieben.nu/liebensraum/2018/04/how-to-grant-oauth2-permissions-to-an-azure-ad-application-using-powershell-unattended-silently/
Function Grant-OAuth2PermissionsToApp{
Param(
[Parameter(Mandatory=$true)]$Username, #global administrator username
[Parameter(Mandatory=$true)]$Password, #global administrator password
[Parameter(Mandatory=$true)]$azureAppId #application ID of the azure application you wish to admin-consent to
)
$secpasswd = ConvertTo-SecureString $Password -AsPlainText -Force
$mycreds = New-Object System.Management.Automation.PSCredential ($Username, $secpasswd)
$res = login-azurermaccount -Credential $mycreds
$context = Get-AzureRmContext
$tenantId = $context.Tenant.Id
$refreshToken = #($context.TokenCache.ReadItems() | where {$_.tenantId -eq $tenantId -and $_.ExpiresOn -gt (Get-Date)})[0].RefreshToken
$body = "grant_type=refresh_token&refresh_token=$($refreshToken)&resource=74658136-14ec-4630-ad9b-26e160ff0fc6"
$apiToken = Invoke-RestMethod "https://login.windows.net/$tenantId/oauth2/token" -Method POST -Body $body -ContentType 'application/x-www-form-urlencoded'
$header = #{
'Authorization' = 'Bearer ' + $apiToken.access_token
'X-Requested-With'= 'XMLHttpRequest'
'x-ms-client-request-id'= [guid]::NewGuid()
'x-ms-correlation-id' = [guid]::NewGuid()
}
$script:url = "https://main.iam.ad.ext.azure.com/api/RegisteredApplications/$azureAppId/Consent?onBehalfOfAll=true"
Invoke-RestMethod -Uri $url -Headers $header -Method POST -ErrorAction Stop
}

Resources