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

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

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.

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

Getting the Secret of a Service Prinicpal in YAML Pipeline (Terraform)

I need to do an Invoke-SQLCmd in Terraform - all fine BUT I need to get the Secret for the service principal (Azure) that is being used throughout the build. So I can use this :
Import-Module SQLServer
# Note: the sample assumes that you or your DBA configured the server to accept connections using
# that Service Principal and has granted it access to the database (in this example at least
# the SELECT permission).
$clientid = "enter application id that corresponds to the Service Principal" # Do not confuse with its display name
$tenantid = "enter the tenant ID of the Service Principal"
$secret = "enter the secret associated with the Service Principal"
$request = Invoke-RestMethod -Method POST `
-Uri "https://login.microsoftonline.com/$tenantid/oauth2/token"`
-Body #{ resource="https://database.windows.net/"; grant_type="client_credentials"; client_id=$clientid; client_secret=$secret }`
-ContentType "application/x-www-form-urlencoded"
$access_token = $request.access_token
# Now that we have the token, we use it to connect to the database 'mydb' on server 'myserver'
Invoke-Sqlcmd -ServerInstance myserver.database.windows.net -Database mydb -AccessToken $access_token`
-query 'select * from Table1'
I can get the cliendId and the TenantID quite easily within PowerShell but I cannot get the secret. So how would i get it ? although i am using the same Service Prinical during the build.
As I have already mentioned you can only retrieve a secret value at the time of creation and after that it becomes hidden . So , its recommended to store the created in some secure place or keyvault.
As you can see for testing I used AzureAD Module and the below script :
## Get APP Details
$APP=Get-AzureADApplication -Filter "DisplayName eq 'ansumanterraformtest'"
## ClientID
Write-Host("clientID : ")$APP.AppId
##TenantID
$tenantID=(Get-AzureADTenantDetail).objectId
Write-Host ("TenantID :")$tenantID
## Get Secret
$getsecret=Get-AzureADApplicationPasswordCredential -ObjectId $APP.ObjectId
if($getsecret.value -ne $null){
Write-Host ("Exisitng $get Secret Value: ")$getsecret.Value
}
else{
Write-Host ("Cannot Retrieve Secret!!!!")
}
Output:
So , As a solution we can create a new secret and retrieve if you don't have it stored in anywhere like below:
$end_date = (get-date).Date.AddDays(365)
## Create new Secret
$createsecret = New-AzureADApplicationPasswordCredential -CustomKeyIdentifier "PowershellKey" -ObjectId $APP.ObjectId -EndDate $end_date
## Secret Value
Write-Host ("Secret Value For new Secret :")$createsecret.value
Output:

Access Azure PIM api in azure pipelines via service principal

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.

Access token validation failure for AD graph API after using the token acquired from AzureRmContext in Powershell

I am trying to make API calls to Microsoft graph API using Oauth2 to log in.
I tried to use AzureRm cmdlet to get the token for my account, so I can make the API calls, but the message "Access token validation failure. Invalid audience." showed up in the JSON response.
Login-AzureRmAccount
$currentAzureContext = Get-AzureRmContext
$tenantId = $currentAzureContext.Tenant.Id
$accountId = $currentAzureContext.Account.Id
$tokenCache = $currentAzureContext.TokenCache
$cachedTokens = $tokenCache.ReadItems() `
| where { $_.TenantId -eq $tenantId }
$accessToken = $cachedTokens.AccessToken
Invoke-RestMethod -Method Get `
-Uri ("https://graph.microsoft.com/v1.0/me") `
-Headers #{ "Authorization" = "Bearer " + $accessToken }
The following is the JSON response:
Invoke-RestMethod : {
"error": {
"code": "InvalidAuthenticationToken",
"message": "Access token validation failure. Invalid audience.",
"innerError": {
"request-id": "8429e520-401b-4382-adad-4f55bccbe752",
"date": "2019-11-04T16:53:27"
}
}
}
Have a look at the token in https://jwt.ms and see what is the aud claim. I think the token you get via AzureRm is an access token to the Azure Management APIs. The value for MS Graph is 'https://graph.microsoft.com'. You can use the AzureAD PS module to get Graph tokens. Also note that AAD is notthe same as MS Graph.

Resources