我需要为创建特定 M365 租户的每个新组创建访问评审。手动创建这些非常耗时,因此我想在 Azure 自动化运行手册中利用 MS Graph API。
然而我遇到了以下错误:
{“error”:{“code”:””,”message”:”试图执行未经授权的操作。”,”innerError”:{“date”:”2024-07-01T13:54:10″,”request-id”:”fbbb3311-e8c8-45b4-b7fd-d077ce23424c”,”client-request-id”:”fbbb3311-e8c8-45b4-b7fd-d077ce23424c”}}}(响应状态代码不表示成功:403(禁止)。)
运行手册从操作组触发,并将组特定详细信息从 webhook 捕获到变量中。我能够使用 MS graph 在运行手册中查询该组。因此通过 graph API 提取数据没有问题。
这是我的 Runbook(注意:我将使用变量来填充用户/组信息,但这已被剥离以便进行故障排除):
<#
.DESCRIPTION
A runbook to create an access review for the given O365 group.
.NOTES
#>
param
(
[Parameter (Mandatory=$false)]
[object] $WebhookData
)
$ErrorActionPreference = 'Stop'
if ($WebhookData){
# Collect properties of WebhookData.
$WebhookName = $WebhookData.WebhookName
$WebhookBody = $WebhookData.RequestBody
$WebhookHeaders = $WebhookData.RequestHeader.Message
# Webhook name that called This
Write-Output "This runbook was started from webhook $WebhookName."
# Convert the WebhookBody into PS object
$WebhookBody = (ConvertFrom-Json -InputObject $WebhookBody)
$URI = $WebhookBody.Data.AlertContext.Condition.AllOf.LinkToSearchResultsApi
$userAssignedMID = "MI-ALL-DEV-AutomationManagedID"
$userAssignedMIDClientID = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" # User Assigned Managed Identity for Automation
$connectionName = "AzureRunAsConnection"
$TenantID = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
$subscriptionID = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
$resourceGroup = "RG-ALL-DEV-ResourceGroup"
#Auth token - HTTP Post
$Url = $env:IDENTITY_ENDPOINT
$Headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
$Headers.Add("Metadata", "True")
$Headers.Add("X-IDENTITY-HEADER", $env:IDENTITY_HEADER)
$Body = @{
'resource'='https://graph.microsoft.com/'
'client_id'=$userAssignedMIDClientID
}
$accessToken = (Invoke-RestMethod $url -Method 'POST' -Headers $Headers -ContentType 'application/x-www-form-urlencoded' -Body $body ).access_token
$authHeader = @{
'Authorization' = "Bearer $accessToken"
}
# Ensure we do not inherit an AzContext in runbook
Disable-AzContextAutosave -Scope Process
# Connect to Azure with user-assigned managed identity
$AzureContext = (Connect-AzAccount -Identity -AccountId $userAssignedMIDClientID).context
$ConnectionResult = Set-AzContext -SubscriptionName $subscriptionID -DefaultProfile $AzureContext
#Pull in alert rule kql query search results via the LoganAlytics API.
$APIdata = Invoke-AzRestMethod -Uri $URI
#Convert Search results content from JSON to PS objects.
$SearchResults = ConvertFrom-Json $APIdata.content
#Set variables from the search data.
$SearchResults = $SearchResults.tables.rows
$GroupName = $SearchResults[0]
$GroupID = $SearchResults[1]
$CreatedBy = $SearchResults[2]
Write-output "####### New Group Created #########"
Write-Output "GroupName: $Groupname, Group ID: $GroupID, Created By: $CreatedBy"
$GroupUrl = 'https://graph.microsoft.com/v1.0/groups/' + $GroupID + '/owners'
Write-Output "Group URL is: $GroupUrl"
#Connect to graph via the rest api. Find and pull back group owner(s).
$Response = Invoke-RestMethod -Uri $GroupUrl -Method Get -Headers @{'Authorization'= "Bearer $accessToken"}
$GroupOwner = $response.value.id
Write-Output "Group owner: $GroupOwner"
#Review all members of a group with the ID collected from the KQL query now in variable $GroupID.
#$GroupOwner is the reviewer
#It recurs monthly and continues indefinitely.
$ReviewName = "AR-" + $Groupname + "-AllGuests"
$ScopeQueryFilter = "`$filter=(userType eq 'Guest')"
$ScopeQuery = '/groups/' + $GroupID + '/transitiveMembers/?' + $ScopeQueryFilter
Write-Output "Scope Query is: $ScopeQuery"
$ReviewersQuery = '/Users/' + $GroupOwner
Write-Output "Reviewer Query is: $ReviewersQuery"
$FallbackReviewersQuery = '/Groups/' + $GroupOwner
Write-Output "Fallback Reviewer Query is: $FallbackReviewersQuery"
$Body = '{
"displayName": "Test Access Review",
"descriptionForAdmins": "scheduled access review for guest users",
"descriptionForReviewers": "If you have any questions, contact xxx@xxx.com",
"scope": {
"@odata.type": "#microsoft.graph.accessReviewQueryScope",
"query": "/groups/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/transitiveMembers",
"queryType": "MicrosoftGraph"
},
"reviewers": [
{
"query": "/users/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"queryType": "MicrosoftGraph"
}
],
"settings": {
"mailNotificationsEnabled": true,
"reminderNotificationsEnabled": true,
"justificationRequiredOnApproval": true,
"defaultDecisionEnabled": false,
"defaultDecision": "None",
"instanceDurationInDays": 3,
"recommendationsEnabled": true,
"recurrence": {
"pattern": {
"type": "absoluteMonthly",
"interval": 3
},
"range": {
"type": "noEnd",
"startDate": "2024-06-01T12:00:00.667Z"
}
}
}
}'
$Headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
$Headers.Add("Authorization", "Bearer $accessToken")
$CreateReview = Invoke-RestMethod -Headers $Headers -Uri "https://graph.microsoft.com/v1.0/identityGovernance/accessReviews/definitions" -Body $Body -Method POST -ContentType "application/json"
Write-Output "Review Result: $CreateReview"
} else {
Write-Output "No data received"
}`
任何帮助均感激不尽。
注意:我已经编辑了这个问题,因为我发现了原始问题的错误(不要假设你知道属性值并忘记你猜到了)。
当前的问题在于身份验证。身份验证适用于初始请求,但我认为我尚未对正确的图形 API 资源进行身份验证以创建访问审查。
有人能帮助我了解我需要使用什么资源进行身份验证吗?
$(function() {
$(“.js-gps-inline-related-questions .spacer”).on(“click”, function () {
fireRelatedEvent($(this).index() + 1, $(this).data(‘question-id’));
});
function fireRelatedEvent(position, questionId) {
StackExchange.using(“gps”, function() {
StackExchange.gps.track(‘related_questions.click’,
{
position: position,
originQuestionId: 78691632,
relatedQuestionId: +questionId,
location: ‘inline’,
source: ‘Baseline_Fallback’
});
});
}
});
function toggleInlineRelated(showMore) {
var inlineRelatedLess = document.getElementById(“inline_related_var_a_less”);
var inlineRelatedMore = document.getElementById(“inline_related_var_a_more”);
var inlineRelatedSeeMore = document.getElementById(“inline_related_see_more”);
var inlineRelatedSeeLess = document.getElementById(“inline_related_see_less”);
if (showMore) {
inlineRelatedLess.classList.add(“d-none”);
inlineRelatedSeeMore.classList.add(“d-none”);
inlineRelatedMore.classList.remove(“d-none”);
inlineRelatedSeeLess.classList.remove(“d-none”);
}
else {
inlineRelatedMore.classList.add(“d-none”);
inlineRelatedSeeLess.classList.add(“d-none”);
inlineRelatedLess.classList.remove(“d-none”);
inlineRelatedSeeMore.classList.remove(“d-none”);
}
}
|