[CLOUDS-8722] Replace deprecated PIM endpoint with Microsoft Graph API - #250
[CLOUDS-8722] Replace deprecated PIM endpoint with Microsoft Graph API#250perzycharles wants to merge 6 commits into
Conversation
…ctive_entra_role_ids The legacy api.azrbac.mspim.azure.com endpoint fails in Cloud Shell because it requires an ARM-audience token (management.core.windows.net) that the PIM endpoint rejects with Unauthorized/BadRequest, and Cloud Shell MSI does not support the correct PIM audience directly. Replace with two Microsoft Graph API calls: - roleAssignments for permanent directory role assignments - roleAssignmentScheduleInstances for PIM-activated time-bound assignments Graph is natively supported by Cloud Shell MSI and is already used by get_entra_role_permissions in the same file. Regular users without elevated roles receive Forbidden on roleAssignmentScheduleInstances (missing RoleManagement.Read.Directory scope), which is caught and treated as an empty set — semantically correct since such users have no PIM-activated roles. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
PCaponetti
left a comment
There was a problem hiding this comment.
The Graph API gets throttled a bunch due to low quota, so adding another call to it is scary. Would it be possible to add a cache to another place where it is called? domains/cloud_platform/azure/apps/resource-collection/.../unifiedroleassignmentscheduleinstances.go is a good candidate.
| # PIM-activated (time-bound) assignments. Regular users without elevated roles lack the | ||
| # RoleManagement.Read.Directory scope to call this endpoint; Forbidden means no PIM roles. | ||
| try: | ||
| pim_active = set( |
| .param("--query", "value[].roleDefinitionId") | ||
| ) | ||
| ) | ||
| except (AccessError, RuntimeError): |
There was a problem hiding this comment.
should we have a catch-all as well? I think 404 escapes this, no?
perzycharles
left a comment
There was a problem hiding this comment.
Thanks for the review! Two points on the throttling concern:
-
Different quota pool: The throttling in
resource-collectionis Datadog's backend service hitting Graph continuously on behalf of many customers. This quickstart runs in the customer's own Cloud Shell using their own Azure credentials — it draws from the customer's tenant quota, entirely separate from Datadog's service quota. -
The second call is effectively free for most users: Regular users (no elevated Entra roles) get
ForbiddenonroleAssignmentScheduleInstancesimmediately since they lackRoleManagement.Read.Directory— no data is fetched. Only users who already hold a privileged Entra role actually execute that call, and the whole function runs at most once per script invocation.
So in practice this adds 0 Graph calls for regular users and 1 call for privileged users, both against the customer's own tenant quota rather than ours.
perzycharles
left a comment
There was a problem hiding this comment.
Thanks for the review! Two points on the throttling concern:
-
Different quota pool: The throttling in
resource-collectionis Datadog's backend service hitting Graph continuously on behalf of many customers. This quickstart runs in the customer's own Cloud Shell using their own Azure credentials — it draws from the customer's tenant quota, entirely separate from Datadog's service quota. -
The second call is effectively free for most users: Regular users (no elevated Entra roles) get
ForbiddenonroleAssignmentScheduleInstancesimmediately since they lackRoleManagement.Read.Directory— no data is fetched. Only users who already hold a privileged Entra role actually execute that call, and the whole function runs at most once per script invocation.
So in practice this adds 0 Graph calls for regular users and 1 call for privileged users, both against the customer's own tenant quota rather than ours.
…_entra_role_ids - Broaden exception catch from (AccessError, RuntimeError) to (AzIntegrationError, RuntimeError) so ResourceNotFoundError (404), InteractiveAuthenticationRequiredError, RefreshTokenError, and other AzIntegrationError subclasses are all handled rather than propagating - Add 7 unit tests covering the happy path, deduplication, and all exception types that should be treated as empty PIM role sets - Rebuild dist bundles Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
perzycharles
left a comment
There was a problem hiding this comment.
Good catches — both addressed in the latest commit:
-
404 / catch-all: Changed
except (AccessError, RuntimeError)toexcept (AzIntegrationError, RuntimeError).ResourceNotFoundError(404),InteractiveAuthenticationRequiredError,RefreshTokenError, and all otherAzIntegrationErrorsubclasses are now caught.RuntimeErrorstays alongside it for the generic fallthrough inexecute_cmd.py. -
Tests: Added 7 unit tests in
tests/test_role_assignments.pycovering the happy path, deduplication, and each exception type (AccessError,ResourceNotFoundError,RuntimeError) being treated as an empty PIM role set.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Motivation
CLOUDS-8722
https://dd.slack.com/archives/C02FPP4JQHX/p1788163680308149
Summary
api.azrbac.mspim.azure.comPIM endpoint inget_active_entra_role_idswith two Microsoft Graph API callsmanagement.core.windows.net) that the PIM endpoint rejects withUnauthorized(BadRequest), and Cloud Shell MSI does not support the correct PIM audienceget_entra_role_permissionsin the same fileTwo Graph calls replace the single broken PIM call:
roleAssignments— permanent directory role assignments (accessible to all users)roleAssignmentScheduleInstances— PIM-activated time-bound assignments (requiresRoleManagement.Read.Directory; regular users receiveForbidden, caught and treated as empty set — semantically correct since users without elevated roles have no PIM-activated roles)Test plan
I didn't have issue when executing the script in CloudShell because
Users can register applicationsis enabled in our sandbox.https://datadoghq.atlassian.net/browse/CLOUDS-8722?focusedCommentId=3601350
However, I had the same Unauthorized error when running the same rest request as our code.
az rest \ --resource "https://management.core.windows.net/" \ -u "https://api.azrbac.mspim.azure.com/api/v2/privilegedAccess/aadroles/roleAssignments?\$select=roleDefinitionId&\$filter=subjectId eq '$(az ad signed-in-user show --query id -o tsv)' and assignmentState eq 'Active'&\$top=999" \ --query "value[].roleDefinitionId"I also run the same rest requests as the fix to verify. I have no PIM-eligible roles configured, so I was never granted the RoleManagement.Read.Directory permission that comes with elevated Entra roles. This is the expected state for a regular user.
Fixes: CLOUDS-8722
🤖 Generated with Claude Code