Training module · Draft
Microsoft Automation, Module 2: Microsoft Entra ID through Microsoft Graph
Learn the Graph APIs, least-privileged permissions and data shapes behind users, groups, Conditional Access, PIM, applications, logs and authentication methods, then run five read-only checks mapped to ScubaGear on fixtures and in your test tenant
Status — Draft
This page is still being written. Expect gaps and changes; it is not part of the workbook's production guidance. Last reviewed 2026-11-07.
Learning path: Microsoft Automation · Module 2 of 4 · 89 minutes · Code: examples/ms-automation (entra_tasks.py, FmwGraph.psm1)
Watch: the narrated deck for this module: Deck · Narrated deck (about 13 minutes, captions) · ▶ Watch · MP4 · Script · Captions.
Unit 1: Introduction
5 minutes, with the objectives and prerequisites
Book 02 builds the Entra ID target, and Book 04 puts access behind Conditional Access, PIM and application governance. Both ask for evidence a script can collect: who hasn’t signed in, what the Conditional Access policies say, who holds a privileged role permanently, which applications still have passwords, and who can’t yet use phishing-resistant MFA.
Your ISSO asks for those answers every month, and today someone gets them by clicking through the admin center and pasting screenshots. In this module you map each question to the Graph API that answers it, the least-privileged permission, and the shape of the data. Five of them are built as tested, read-only checks, in Python and PowerShell, which you run in the labs. They’re illustrative: unit-tested against fixtures, not proven against live tenants.
The federal requirement. BOD 25-01 makes ScubaGear’s mandatory Entra ID policies required for in-scope tenants, with continuous reporting to CISA. Three of the checks produce evidence for those policies (four results): Conditional Access settings (MS.AAD.1.1v1, 2.1v1, 2.3v1, 3.1v1 and 3.6v1), the Global Administrator count (MS.AAD.7.1v1), permanent active privileged assignments (MS.AAD.7.4v1) and phishing-resistant registration (toward MS.AAD.3.1v1 and 3.6v1). The application-credential check reads MS.AAD.5.5v1 to 5.7v1, and the stale-user check supports Book 02’s account lifecycle. In NIST SP 800-53 terms that’s account management (AC-2), least privilege for privileged accounts (AC-6(5)), multifactor authentication (IA-2(1), IA-2(2)) and continuous monitoring (CA-7). Don’t restate them: which policies are mandatory, and the control mapping, are in Book 02, Appendix A, and OMB M-22-09’s phishing-resistant requirement is in its M-22-09 section.
Learning objectives
In this module, you’ll:
- Name the Graph API and least-privileged application permission for each of Book 02’s and Book 04’s evidence questions.
- Read sign-in activity, group membership, Conditional Access state, PIM schedules, application credentials and registration details, and the traps in each.
- Explain why Entra ID’s own log retention means a script checks logs but doesn’t keep them.
- Say which Entra ID features differ in GCC High and DoD, and why the table doesn’t apply to GCC.
- Run the five checks on fixtures in Python and PowerShell, and read each finding against its ScubaGear policy.
Prerequisites
Unit 2: The APIs at a glance
8 minutes
Every API below is generally available in Graph v1.0. Each one’s reference page lists it as available in the global service (which GCC uses), US Government L4 (GCC High) and L5 (DoD).1 The permission is the least-privileged application permission the page lists. The Book column is where the workbook uses it.
| Question | Graph API (v1.0) | Least-privileged application permission | Book |
|---|---|---|---|
| Who is in the directory, and when did they last sign in? | GET /users with $select=...,signInActivity |
User.Read.All, plus AuditLog.Read.All for signInActivity2 |
B02 Ch 10 |
| What changed since the last run? | GET /users/delta |
User.Read.All3 |
Module 1 |
| Who is in a group, including nested groups? | GET /groups/{id}/transitiveMembers |
GroupMember.ReadBasic.All4 |
B04 Ch 10 |
| What do the Conditional Access policies say? | GET /identity/conditionalAccess/policies and /namedLocations |
Policy.Read.All56 |
B04 Ch 07, 10 |
| Create or change a Conditional Access policy | POST /identity/conditionalAccess/policies |
Policy.Read.All and Policy.ReadWrite.ConditionalAccess7 |
B04 Ch 10 |
| Who is eligible for a privileged role? | GET /roleManagement/directory/roleEligibilitySchedules |
RoleEligibilitySchedule.Read.Directory8 |
B04 Ch 08 |
| Who holds a role now, and until when? | GET /roleManagement/directory/roleAssignmentSchedules |
RoleAssignmentSchedule.Read.Directory9 |
B04 Ch 08 |
| What are a role’s PIM settings? | GET /policies/roleManagementPolicyAssignments |
RoleManagementPolicy.Read.Directory10 |
B04 App B |
| Which applications have passwords or certificates, and when do they expire? | GET /applications with $select=...,passwordCredentials,keyCredentials |
Application.Read.All11 |
B04 Ch 05 |
| Who has been given an application’s roles? | GET /servicePrincipals/{id}/appRoleAssignedTo |
Application.Read.All12 |
B04 Ch 05 |
| Which delegated permissions have been consented? | GET /oauth2PermissionGrants |
Directory.Read.All13 |
B04 Ch 05 |
| What happened in the directory? | GET /auditLogs/directoryAudits |
AuditLog.Read.All14 |
B02 Ch 10 |
| Who signed in, and which policies applied? | GET /auditLogs/signIns |
AuditLog.Read.All15 |
B02 Ch 10 |
| Who has registered which authentication methods? | GET /reports/authenticationMethods/userRegistrationDetails |
AuditLog.Read.All16 |
B04 Ch 07 |
| Which methods does the tenant allow? | GET /policies/authenticationMethodsPolicy |
Policy.Read.AuthenticationMethod17 |
B02 Ch 10 |
| Who does ID Protection consider risky? | GET /identityProtection/riskyUsers |
IdentityRiskyUser.Read.All18 |
B04 Ch 03 |
Unit 3: Users, groups and Conditional Access
12 minutes
Users and sign-in activity
signInActivity is how a script finds dormant accounts. It holds lastSignInDateTime (interactive) and lastNonInteractiveSignInDateTime. Microsoft notes that some users reach resources only through clients, so the non-interactive date belongs in any inactivity check alongside the interactive one.19 Three constraints:
- License and permission. Details for
signInActivityrequire Microsoft Entra ID P1 or P2 and theAuditLog.Read.Allpermission.20 - Page size. Selecting or filtering on
signInActivitycaps the page at 500 users, against 999 otherwise.21 - It must be selected. The property is returned only when it’s named in
$select.22
For change tracking, /users/delta follows the Module 1 pattern. Any query parameter other than $deltatoken and $skiptoken has to be in the first delta request, and Graph carries it in the links that follow.23
Groups and membership
Use transitiveMembers when the question is who is effectively in a group, nested groups included; members returns only direct members. Which features honor nested membership varies by feature, so check each feature’s documentation before assuming either way (design guidance). The transitive call costs 5 resource units against 3 for direct members (Module 1). GroupMember.ReadBasic.All is enough to read membership. Members of a type the app has no permission to read come back with only id and @odata.type.24
Conditional Access
Reading needs only Policy.Read.All.25 Each policy’s state is enabled, disabled or enabledForReportingButNotEnforced, the last being report-only.26 Book 04’s rollout depends on knowing which policies are report-only. CA022, phishing-resistant MFA for every member, starts that way.
Creating a policy needs Policy.Read.All and Policy.ReadWrite.ConditionalAccess. Microsoft notes “a known permissions issue” that may need consent to more permissions. For a person signing in, the least-privileged roles are Conditional Access Administrator and Security Administrator.27 Book 04 Chapter 10 keeps the policies as code. Here that means a pipeline holds the write permission, and the evidence collector holds only the read (design guidance).
The ca-export check writes one JSON file per policy with sorted keys, so a policy change shows up as a readable diff between two exports.
Unit 4: Privileged Identity Management and applications
12 minutes
Privileged Identity Management
The current PIM APIs are under /roleManagement/directory.28 Two kinds of schedule matter:
- Eligibility schedules (
roleEligibilitySchedules): who may activate a role.29 - Assignment schedules (
roleAssignmentSchedules): who holds it now.assignmentTypeisAssignedfor a direct assignment andActivatedfor an activation.memberTypesays whether the assignment isDirect, through aGroup, orInherited.30
An assignment’s end is in scheduleInfo.expiration. Its type is notSpecified, noExpiration, afterDateTime or afterDuration.31 An Assigned schedule with no expiration is permanent active, which is what ScubaGear MS.AAD.7.4v1 forbids for highly privileged roles.
Changes go through schedule requests (roleAssignmentScheduleRequests), whose action is one of the following:32
- an administrator’s
adminAssign,adminUpdate,adminRemove,adminExtendoradminRenew; - a user’s
selfActivate,selfDeactivate,selfExtendorselfRenew.
Writes need RoleAssignmentSchedule.ReadWrite.Directory. For a signed-in person, the least-privileged role for writes is Privileged Role Administrator.33 Microsoft describes PIM as “a feature of Microsoft Entra ID Governance”.34 Role IDs are stable built-in template IDs: Global Administrator is 62e90394-69f5-4237-9190-012177145e10 and Privileged Role Administrator is e8611ab8-c189-46e8-94e1-60213ab1f814.35
Applications and their credentials
An application object’s passwordCredentials (secrets) and keyCredentials (certificates) each carry startDateTime and endDateTime, in UTC.3637 Those dates are enough to test ScubaGear’s three application rules:38
- no application passwords (MS.AAD.5.5v1);
- passwords valid for at most 180 days (MS.AAD.5.6v1);
- certificates valid for at most 365 days (MS.AAD.5.7v1).
Microsoft’s app management policies enforce limits going forward. ScubaGear’s certificate limit is 365 days, but the program caps certificates at 180 days through those policies (Book 04, Chapter 05), and the check flags only ScubaGear’s limit. The check finds what existed before they were turned on.
Two related reads for consent review (Book 04, Chapter 05):
Unit 5: Logs and authentication methods
8 minutes
Logs and retention
Directory audits and sign-ins both need AuditLog.Read.All.4142 A sign-in’s appliedConditionalAccessPolicies is filled in only for callers allowed to read Conditional Access data. A log reader without that permission sees sign-ins but not which policies applied.43
Entra ID keeps audit logs and sign-ins for seven days on the Free tier and 30 days with P1 or P2.44 That’s why ScubaGear MS.AAD.4.1v1 requires sending the logs to the security operations center, as the crosswalk notes. A Graph script is a way to check logs, not to keep them.
Authentication methods
userRegistrationDetails gives one row per user with isMfaRegistered, isPasswordlessCapable, isAdmin, userType (member or guest) and methodsRegistered.45 It’s the quickest way to track progress toward ScubaGear MS.AAD.3.1v1, phishing-resistant MFA for all users.
One caution. Microsoft documents methodsRegistered only by example: “mobilePhone, email, passKeyDeviceBound”.46 A check that counts phishing-resistant users has to choose which method names to count, and should be confirmed against a real report (inference). The example code keeps that list in one place for that reason.
Reading one user’s registered methods needs UserAuthenticationMethod.Read.All. That API isn’t available in the China cloud, though it is in GCC High and DoD.47 The tenant’s allowed methods come from the authentication methods policy, which needs Policy.Read.AuthenticationMethod.48
Unit 6: Where agencies are
6 minutes
The starting state. Most agencies on this path run a Microsoft 365 GCC tenant, synchronized from Active Directory, with Conditional Access in place and some policies still report-only, permanent role assignments left from before PIM, and applications that still hold client secrets. Some run GCC High. Book 02, Appendix A filters every identity control by government-cloud availability, licensing and Active Directory friction.
What differs in US Government clouds. The APIs in Unit 2 are the same in GCC High and DoD. The endpoints differ (Module 1), and so do some features. Microsoft’s US Government feature table for Entra ID, as of January 2026, lists:49
- Available:
- Conditional Access;
- PIM;
- access reviews;
- entitlement management;
- Lifecycle Workflows;
- ID Protection’s risky-account reports.
- Partial: HR-driven provisioning, which depends on the HR app.
- Not available:
- the My Staff delegated user management portal;50
- the Microsoft Entra threat intelligence risk detection.
A GCC tenant uses the commercial Entra ID service, so this table doesn’t apply to it.
One Conditional Access difference matters for the export. Multifactor authentication’s Trusted IPs feature isn’t supported in Azure Government; Microsoft’s guidance is to use Conditional Access policies with named locations instead.51 In a GCC High or DoD tenant, then, the named locations that ca-export writes are where network-based MFA exceptions live.
Interim paths (design guidance):
- Phishing-resistant MFA isn’t enforced for everyone yet. Run
mfa-registrationon a schedule and keep CA022 report-only until the counts say it’s safe, as Book 04 rolls it out: it enforces CA022 by shrinking a time-bound not-ready exclusion group; Book 02’s appendix covers the alternative MFA policy required in the meantime. - Permanent privileged assignments remain.
pim-reportlists them; move each one to eligible in PIM through the change process, and run the report again to show it. - Applications still have secrets.
app-credentialslists them with their lifetimes; replace each with a certificate or federated credential (Module 1), then turn on app management policies. - Logs aren’t leaving Entra ID yet. Treat that as the first fix: the retention window above is the evidence you lose each day.
Unit 7: The five checks
5 minutes
Each check is read-only and tied to the ScubaGear policy it evidences. The Python and PowerShell versions produce the same results for the same fixtures.
| Check | What it reports | ScubaGear |
|---|---|---|
stale-users |
Enabled members with no interactive or non-interactive sign-in in 90 days; never-signed-in accounts older than 90 days | Supports Book 02’s account lifecycle (no ScubaGear policy) |
ca-export |
Every policy as sorted JSON, plus counts of enabled, report-only and disabled | Evidence for MS.AAD.1.1v1, 2.1v1, 2.3v1, 3.1v1 and 3.6v1 settings |
pim-report |
Global Administrator count; every permanent active assignment to a role you name as privileged | MS.AAD.7.1v1, MS.AAD.7.4v1 |
app-credentials |
Every secret and certificate with its lifetime and days to expiry | MS.AAD.5.5v1, 5.6v1, 5.7v1 |
mfa-registration |
Members without MFA, without a phishing-resistant method, and administrators without one | Toward MS.AAD.3.1v1 and 3.6v1 |
The permissions for all five together are:
User.Read.All,AuditLog.Read.AllandPolicy.Read.All;RoleManagement.Read.Directory,RoleEligibilitySchedule.Read.DirectoryandRoleAssignmentSchedule.Read.Directory;Application.Read.All.
That’s a reader’s set: none of them can change the tenant. Grant only the ones for the checks you run.
Unit 8: Exercise: Lab 1 (fixtures, no tenant)
25 minutes
Every command in this lab, in order, from examples/ms-automation. Each step below says what it does and what to look for.
In a POSIX shell (Git Bash on Windows):
# Step 1
python python/entra_tasks.py --from-json tests/fixtures/users.json --as-of 2026-10-25T00:00:00Z stale-users
# Step 1
python -c "import json; [print(u['userPrincipalName'], u['accountEnabled'], u['userType'], u['createdDateTime'], (u.get('signInActivity') or {}).get('lastSignInDateTime'), (u.get('signInActivity') or {}).get('lastNonInteractiveSignInDateTime')) for u in json.load(open('tests/fixtures/users.json'))['users']]"
# Step 2
python python/entra_tasks.py --from-json tests/fixtures/ca.json ca-export --out out/ca
# Step 3
python python/entra_tasks.py --from-json tests/fixtures/pim.json pim-report --role e8611ab8-c189-46e8-94e1-60213ab1f814
# Step 4
python python/entra_tasks.py --from-json tests/fixtures/apps.json --as-of 2026-10-25T00:00:00Z app-credentials
# Step 5
python python/entra_tasks.py --from-json tests/fixtures/mfa.json --as-of 2026-10-25T00:00:00Z mfa-registrationIn PowerShell 7:
# Step 6
Import-Module ./powershell/FmwGraph.psm1
$d = Get-Content -Raw tests/fixtures/pim.json | ConvertFrom-Json -AsHashtable -DateKind String
Test-FmwPim -RoleDefinitions $d.roleDefinitions -Eligibility $d.eligibility -Assignments $d.assignments -PrivilegedRoleIds 62e90394-69f5-4237-9190-012177145e10, e8611ab8-c189-46e8-94e1-60213ab1f814 | ConvertTo-Json -Depth 5
$u = Get-Content -Raw tests/fixtures/users.json | ConvertFrom-Json -AsHashtable -DateKind String
Test-FmwStaleUser -Users $u.users -AsOf '2026-10-25T00:00:00Z' -Days 90 | ConvertTo-JsonIn a POSIX shell (Git Bash on Windows):
# Step 7
python -m pytest -q -p no:cacheprovider tests/python/test_graphkit_and_tasks.py
rm -rf outYou run each check against the kit’s fictional tenant and read each finding against its policy. Work in examples/ms-automation. --from-json evaluates a saved Graph response instead of calling Graph, and --as-of fixes the evaluation time so your output matches this page.
Find the stale accounts.
python python/entra_tasks.py --from-json tests/fixtures/users.json --as-of 2026-10-25T00:00:00Z stale-usersExpected output:
[ { "id": "u3", "userPrincipalName": "cy@contoso.example", "lastSignIn": null, "daysIdle": null }, { "id": "u2", "userPrincipalName": "ben@contoso.example", "lastSignIn": "2026-07-01T00:00:00Z", "daysIdle": 116 }, { "id": "u7", "userPrincipalName": "fay@contoso.example", "lastSignIn": "2026-07-27T00:00:00Z", "daysIdle": 90 } ]cyhas never signed in and is older than 90 days. List each user intests/fixtures/users.jsonwith its state, type, creation date and the two sign-in dates:python -c "import json; [print(u['userPrincipalName'], u['accountEnabled'], u['userType'], u['createdDateTime'], (u.get('signInActivity') or {}).get('lastSignInDateTime'), (u.get('signInActivity') or {}).get('lastNonInteractiveSignInDateTime')) for u in json.load(open('tests/fixtures/users.json'))['users']]"ben’s interactive sign-in was May 1, but hislastSignInis July 1, his non-interactive date, because the check uses the later of the two.deehas never signed in either, but her account is less than 90 days old;eveis disabled; and the guest isn’t a member. None of them is listed.Export Conditional Access.
python python/entra_tasks.py --from-json tests/fixtures/ca.json ca-export --out out/caThe summary lists three policies and ends with the counts:
"counts": { "enabled": 1, "enabledForReportingButNotEnforced": 1, "disabled": 1 }CA022, phishing-resistant MFA for every member, shows
"reportOnly": true. The folderout/canow holdsnamed-locations.json,p-legacy.json,p-old.jsonandp-pr.json: one sorted file per policy, ready to diff.Report privileged assignments for Global Administrator and Privileged Role Administrator.
python python/entra_tasks.py --from-json tests/fixtures/pim.json pim-report --role e8611ab8-c189-46e8-94e1-60213ab1f814Expected output:
{ "globalAdministrators": 3, "MS.AAD.7.1v1": "pass", "permanentActive": [ { "principalId": "bg1", "roleDefinitionId": "62e90394-69f5-4237-9190-012177145e10", "role": "Global Administrator", "memberType": "Direct" }, { "principalId": "x9", "roleDefinitionId": "e8611ab8-c189-46e8-94e1-60213ab1f814", "role": "Privileged Role Administrator", "memberType": "Direct" } ], "MS.AAD.7.4v1": "fail" }Global Administrator is always in the list;
--roleadds Privileged Role Administrator. Three Global Administrators is within ScubaGear’s range of two to eight, so 7.1 passes. (The program’s own target, from Learn and Book 04, is fewer than five, so a tenant with six passes here and fails the Privileged Access path’s PA-INV check.) Two permanent active assignments fail 7.4.Check application credentials.
python python/entra_tasks.py --from-json tests/fixtures/apps.json --as-of 2026-10-25T00:00:00Z app-credentialsExpected: five credentials, most urgent first. The first is
Legacy report’s passwordk4, with"lifetimeDays": 92,"daysLeft": -54and the flagsMS.AAD.5.5v1andexpired.HR connector’s passwordk1(365 days) carriesMS.AAD.5.5v1andMS.AAD.5.6v1. Two certificates carryMS.AAD.5.7v1:k3(374 days, alsoexpiring) andk5(1,827 days). Only certificatek2, 365 days, has no flag (the check flags only ScubaGear’s 365-day limit, not the program’s 180-day cap).Check MFA registration.
python python/entra_tasks.py --from-json tests/fixtures/mfa.json --as-of 2026-10-25T00:00:00Z mfa-registrationExpected output:
{ "members": 4, "notMfaRegistered": 1, "noPhishingResistantMethod": 2, "admins": 2, "adminsNoPhishingResistantMethod": [ "ben@contoso.example" ] }Guests aren’t counted as members.
benis an administrator without a phishing-resistant method: a finding against MS.AAD.3.6v1.Run two of the same checks in PowerShell. Start
pwshinexamples/ms-automation, then:Import-Module ./powershell/FmwGraph.psm1 $d = Get-Content -Raw tests/fixtures/pim.json | ConvertFrom-Json -AsHashtable -DateKind String Test-FmwPim -RoleDefinitions $d.roleDefinitions -Eligibility $d.eligibility -Assignments $d.assignments -PrivilegedRoleIds 62e90394-69f5-4237-9190-012177145e10, e8611ab8-c189-46e8-94e1-60213ab1f814 | ConvertTo-Json -Depth 5 $u = Get-Content -Raw tests/fixtures/users.json | ConvertFrom-Json -AsHashtable -DateKind String Test-FmwStaleUser -Users $u.users -AsOf '2026-10-25T00:00:00Z' -Days 90 | ConvertTo-JsonExpected: the same JSON as steps 3 and 1. The Pester tests compare every PowerShell check with the Python results in
tests/fixtures/expected.json, so the two versions can’t drift apart.Run the Entra ID tests, then delete the
outfolder.python -m pytest -q -p no:cacheprovider tests/python/test_graphkit_and_tasks.py rm -rf outExpected:
20 passed.
Check your work: for each finding, you can name the ScubaGear policy it evidences and the Graph property it came from.
Unit 9: Exercise: Lab 2 (your test tenant)
Not timed. Not run by the authors.
Every command in this lab, in order, from examples/ms-automation. Each step below says what it does and what to look for.
In a POSIX shell (Git Bash on Windows):
# Step 2: Grant only the application permissions for the checks you run
python -c "import sys; sys.path.insert(0, 'python'); from graphkit import load_calls, permissions_for; [print(t, permissions_for(load_calls(t))) for t in ('stale-users', 'ca-export', 'pim-report', 'app-credentials', 'mfa-registration')]"
# Step 3: Run the checks
pip install azure-identity
python python/entra_tasks.py --cloud gcc --tenant <tenant-id> --client <app-id> --cert app.pem stale-users
python python/entra_tasks.py --cloud gcc --tenant <tenant-id> --client <app-id> --cert app.pem ca-export --out ca
python python/entra_tasks.py --cloud gcc --tenant <tenant-id> --client <app-id> --cert app.pem pim-report
python python/entra_tasks.py --cloud gcc --tenant <tenant-id> --client <app-id> --cert app.pem app-credentials
python python/entra_tasks.py --cloud gcc --tenant <tenant-id> --client <app-id> --cert app.pem mfa-registrationIn PowerShell 7:
# Step 3: Run the checks
Import-Module ./powershell/FmwGraph.psm1
Connect-FmwGraph -Cloud gcc -TenantId <tenant-id> -ClientId <app-id> -CertificateThumbprint <thumbprint>
Invoke-FmwEntraTask -Task pim-reportIn a POSIX shell (Git Bash on Windows):
# Step 5: Back it out
rm -rf caYou run the same checks read-only against a test tenant and compare them with the Microsoft Entra admin center.
- You need: a test tenant in the worldwide or GCC cloud, never production, with fictional users; Microsoft Entra ID P1 or P2 for
signInActivityas Unit 3 notes, and Microsoft Entra ID P2 or Microsoft Entra ID Governance for PIM data; a Privileged Role Administrator, in practice typically a Global Administrator, to grant admin consent (Module 1); Python with theazure-identitypackage (step 3 installs it), or Microsoft Graph PowerShell. - You’ll replace:
<tenant-id>(the test tenant’s ID),<app-id>(the app’s application (client) ID),<thumbprint>(its certificate’s thumbprint, for PowerShell);app.pemis the PEM file with the certificate and its private key. Work inexamples/ms-automation.
Register one app in the tenant’s own cloud, with a certificate credential and no secret.
Grant only the application permissions for the checks you run, and admin consent:
Check Permissions stale-usersUser.Read.All,AuditLog.Read.Allca-exportPolicy.Read.Allpim-reportRoleManagement.Read.Directory,RoleEligibilitySchedule.Read.Directory,RoleAssignmentSchedule.Read.Directoryapp-credentialsApplication.Read.Allmfa-registrationAuditLog.Read.AllThese are the lists
permissions_for(load_calls('<task>'))prints fromgraph-calls.json:python -c "import sys; sys.path.insert(0, 'python'); from graphkit import load_calls, permissions_for; [print(t, permissions_for(load_calls(t))) for t in ('stale-users', 'ca-export', 'pim-report', 'app-credentials', 'mfa-registration')]"Run the checks. In Python, one command per task (
ca-exportneeds--out <folder>):pip install azure-identity python python/entra_tasks.py --cloud gcc --tenant <tenant-id> --client <app-id> --cert app.pem stale-users python python/entra_tasks.py --cloud gcc --tenant <tenant-id> --client <app-id> --cert app.pem ca-export --out ca python python/entra_tasks.py --cloud gcc --tenant <tenant-id> --client <app-id> --cert app.pem pim-report python python/entra_tasks.py --cloud gcc --tenant <tenant-id> --client <app-id> --cert app.pem app-credentials python python/entra_tasks.py --cloud gcc --tenant <tenant-id> --client <app-id> --cert app.pem mfa-registrationThe client checks the token’s permissions first: a missing one stops the run before any Graph call, and an extra one prints a warning. In PowerShell:
Import-Module ./powershell/FmwGraph.psm1 Connect-FmwGraph -Cloud gcc -TenantId <tenant-id> -ClientId <app-id> -CertificateThumbprint <thumbprint> Invoke-FmwEntraTask -Task pim-reportCompare each result with the admin center: the Global Administrator count and permanent assignments in PIM, the policy states in Conditional Access, and the credentials under App registrations. Record which method names your tenant’s registration report uses (Unit 5).
Back it out. Remove the permissions, delete the app registration and its certificate, and delete the output folders and saved results: they hold the tenant’s user names and policies.
rm -rf ca
Unit 10: Knowledge check
5 minutes
- A user’s interactive sign-in was 120 days ago, but a client signed in for them non-interactively last week. Does
stale-usersreport them?- Yes: the interactive sign-in is older than 90 days.
- No: the check uses the later of the two dates.
- Only if the account was created in the last 90 days.
- A role assignment schedule has
assignmentTypeAssignedand an expirationtypeofnoExpiration. What is it?- An eligible assignment.
- A permanent active assignment, which MS.AAD.7.4v1 forbids for highly privileged roles.
- An activation that ends after its duration.
- Why doesn’t a Graph script replace sending the logs to the security operations center?
- Entra ID keeps audit and sign-in logs for at most 30 days, with P1 or P2.
- Graph can’t read sign-in logs.
AuditLog.Read.Allcan’t be granted to an app.
- Which least-privileged application permission does
ca-exportneed?Policy.ReadWrite.ConditionalAccessDirectory.Read.AllPolicy.Read.All
- Your tenant is GCC High. Where should network-based MFA exceptions live?
- In MFA’s Trusted IPs setting.
- In Conditional Access named locations, because Trusted IPs isn’t supported in Azure Government.
- Nowhere: GCC High doesn’t support Conditional Access.
- b. Microsoft notes that some users reach resources only through clients, so the check uses both dates. See Users and sign-in activity, and
benin Lab 1, step 1. - b. An
Assignedschedule with no expiration is permanent active. See Privileged Identity Management. - a. Seven days on Free and 30 with P1 or P2; that’s why MS.AAD.4.1v1 sends logs to the SOC. See Logs and retention.
- c. Reading needs only
Policy.Read.All; writing belongs in a pipeline. See Conditional Access. - b. Microsoft’s guidance for Azure Government is named locations in Conditional Access, which is available there. See Unit 6: Where agencies are.
Unit 11: Summary
3 minutes
You mapped Book 02’s and Book 04’s evidence questions to Graph APIs and their least-privileged permissions, learned the traps in sign-in activity, group membership, PIM schedules, credentials and registration details, and saw what differs in GCC High and DoD. In Lab 1 you ran the five checks on fixtures in Python and PowerShell and read each finding against its ScubaGear policy.
Evidence an ISSO keeps from this module: each check’s dated output, with the app’s permissions and consent record; the ca-export folder in Git, so every policy change is a diff; the PIM and application-credential findings with the change records that closed them; and the ScubaGear results beside them for the policies BOD 25-01 makes mandatory, as Book 02, Appendix A lists them. Book 04, Chapter 10 assembles the package for a period.
Status and review
Draft. Every API, permission, property and limit was checked against Microsoft Learn and the ScubaGear baseline on October 25, 2026. The Trusted IPs difference in Azure Government was added on November 1, 2026 with its own source. Inferences and design guidance are labeled. The checks are tested against fixtures only, and the Lab 1 commands were run on November 1, 2026; the output shown is what they printed. The verification log lists the November 1 additions.
To move this page to In review: - a reviewer confirms the permissions table; - someone runs the five checks against a test tenant and compares the results with the Entra admin center.
To move it to Proved: record that comparison here, including which method names your tenant’s registration report uses. Comments and corrections are welcome as issues on GitHub.
Related: Module 1: Foundations, the Microsoft practice crosswalk, Book 02 — Identity & Directory and Book 04 — Access & Privilege.
Footnotes
The national cloud deployment table on each API’s Microsoft Graph v1.0 reference page, cited for each API below, accessed October 25, 2026.↩︎
Microsoft Learn, “List users” (updated May 16, 2025), https://learn.microsoft.com/en-us/graph/api/user-list, accessed October 25, 2026.↩︎
Microsoft Learn, “user: delta” (updated October 22, 2024), https://learn.microsoft.com/en-us/graph/api/user-delta, accessed October 25, 2026.↩︎
Microsoft Learn, “List group transitive members” (updated May 20, 2024), https://learn.microsoft.com/en-us/graph/api/group-list-transitivemembers, accessed October 25, 2026.↩︎
Microsoft Learn, “List policies” (Conditional Access; updated October 17, 2024), https://learn.microsoft.com/en-us/graph/api/conditionalaccessroot-list-policies, accessed October 25, 2026.↩︎
Microsoft Learn, “List namedLocations” (updated October 17, 2024), https://learn.microsoft.com/en-us/graph/api/conditionalaccessroot-list-namedlocations, accessed October 25, 2026.↩︎
Microsoft Learn, “Create conditionalAccessPolicy” (updated October 17, 2024), https://learn.microsoft.com/en-us/graph/api/conditionalaccessroot-post-policies, accessed October 25, 2026.↩︎
Microsoft Learn, “List roleEligibilitySchedules” (updated April 4, 2024), https://learn.microsoft.com/en-us/graph/api/rbacapplication-list-roleeligibilityschedules, accessed October 25, 2026.↩︎
Microsoft Learn, “List roleAssignmentSchedules” (updated April 4, 2024), https://learn.microsoft.com/en-us/graph/api/rbacapplication-list-roleassignmentschedules, accessed October 25, 2026.↩︎
Microsoft Learn, “List roleManagementPolicyAssignments” (updated April 4, 2024), https://learn.microsoft.com/en-us/graph/api/policyroot-list-rolemanagementpolicyassignments, accessed October 25, 2026.↩︎
Microsoft Learn, “List applications” (updated May 16, 2025), https://learn.microsoft.com/en-us/graph/api/application-list, accessed October 25, 2026.↩︎
Microsoft Learn, “List appRoleAssignments granted for a service principal” (updated October 15, 2024), https://learn.microsoft.com/en-us/graph/api/serviceprincipal-list-approleassignedto, accessed October 25, 2026.↩︎
Microsoft Learn, “List oAuth2PermissionGrants (delegated permission grants)” (updated July 11, 2024), https://learn.microsoft.com/en-us/graph/api/oauth2permissiongrant-list, accessed October 25, 2026.↩︎
Microsoft Learn, “List directoryAudits” (updated August 13, 2024), https://learn.microsoft.com/en-us/graph/api/directoryaudit-list, accessed October 25, 2026.↩︎
Microsoft Learn, “List signIns” (updated July 30, 2024), https://learn.microsoft.com/en-us/graph/api/signin-list, accessed October 25, 2026.↩︎
Microsoft Learn, “List userRegistrationDetails” (updated July 30, 2024), https://learn.microsoft.com/en-us/graph/api/authenticationmethodsroot-list-userregistrationdetails, accessed October 25, 2026.↩︎
Microsoft Learn, “Get authenticationMethodsPolicy” (updated May 1, 2024), https://learn.microsoft.com/en-us/graph/api/authenticationmethodspolicy-get, accessed October 25, 2026.↩︎
Microsoft Learn, “List riskyUsers” (updated October 17, 2024), https://learn.microsoft.com/en-us/graph/api/riskyuser-list, accessed October 25, 2026.↩︎
Microsoft Learn, “signInActivity resource type” (updated July 22, 2024), https://learn.microsoft.com/en-us/graph/api/resources/signinactivity, accessed October 25, 2026.↩︎
Microsoft Learn, “List users” (updated May 16, 2025), https://learn.microsoft.com/en-us/graph/api/user-list, accessed October 25, 2026.↩︎
Microsoft Learn, “List users” (updated May 16, 2025), https://learn.microsoft.com/en-us/graph/api/user-list, accessed October 25, 2026.↩︎
Microsoft Learn, “user resource type” (signInActivity; updated January 10, 2025), https://learn.microsoft.com/en-us/graph/api/resources/user, accessed October 25, 2026.↩︎
Microsoft Learn, “user: delta” (updated October 22, 2024), https://learn.microsoft.com/en-us/graph/api/user-delta, accessed October 25, 2026.↩︎
Microsoft Learn, “List group transitive members” (updated May 20, 2024), https://learn.microsoft.com/en-us/graph/api/group-list-transitivemembers, accessed October 25, 2026.↩︎
Microsoft Learn, “List policies” (Conditional Access; updated October 17, 2024), https://learn.microsoft.com/en-us/graph/api/conditionalaccessroot-list-policies, accessed October 25, 2026.↩︎
Microsoft Learn, “conditionalAccessPolicy resource type” (updated July 22, 2024), https://learn.microsoft.com/en-us/graph/api/resources/conditionalaccesspolicy, accessed October 25, 2026.↩︎
Microsoft Learn, “Create conditionalAccessPolicy” (updated October 17, 2024), https://learn.microsoft.com/en-us/graph/api/conditionalaccessroot-post-policies, accessed October 25, 2026.↩︎
Microsoft Learn, “Manage Microsoft Entra role assignments using the PIM APIs” (updated August 29, 2025), https://learn.microsoft.com/en-us/graph/api/resources/privilegedidentitymanagementv3-overview, accessed October 25, 2026.↩︎
Microsoft Learn, “List roleEligibilitySchedules” (updated April 4, 2024), https://learn.microsoft.com/en-us/graph/api/rbacapplication-list-roleeligibilityschedules, accessed October 25, 2026.↩︎
Microsoft Learn, “unifiedRoleAssignmentSchedule resource type” (updated June 12, 2024), https://learn.microsoft.com/en-us/graph/api/resources/unifiedroleassignmentschedule, accessed October 25, 2026.↩︎
Microsoft Learn, “expirationPattern resource type” (updated July 26, 2024), https://learn.microsoft.com/en-us/graph/api/resources/expirationpattern, accessed October 25, 2026.↩︎
Microsoft Learn, “Create roleAssignmentScheduleRequests” (updated April 4, 2024), https://learn.microsoft.com/en-us/graph/api/rbacapplication-post-roleassignmentschedulerequests, accessed October 25, 2026.↩︎
Microsoft Learn, “Create roleAssignmentScheduleRequests” (updated April 4, 2024), https://learn.microsoft.com/en-us/graph/api/rbacapplication-post-roleassignmentschedulerequests, accessed October 25, 2026.↩︎
Microsoft Learn, “Manage Microsoft Entra role assignments using the PIM APIs” (updated August 29, 2025), https://learn.microsoft.com/en-us/graph/api/resources/privilegedidentitymanagementv3-overview, accessed October 25, 2026.↩︎
Microsoft Learn, “Microsoft Entra built-in roles” (template IDs; updated July 17, 2026), https://learn.microsoft.com/en-us/entra/identity/role-based-access-control/permissions-reference, accessed October 25, 2026.↩︎
Microsoft Learn, “passwordCredential resource type” (updated July 29, 2024), https://learn.microsoft.com/en-us/graph/api/resources/passwordcredential, accessed October 25, 2026.↩︎
Microsoft Learn, “keyCredential resource type” (updated September 26, 2024), https://learn.microsoft.com/en-us/graph/api/resources/keycredential, accessed October 25, 2026.↩︎
CISA, ScubaGear Microsoft Entra ID baseline (
aad.md, released with v2.0.0), policies MS.AAD.3.1v1, 3.6v1, 4.1v1, 5.5v1 to 5.7v1, 7.1v1 and 7.4v1, https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/aad.md, accessed October 25, 2026.↩︎Microsoft Learn, “List appRoleAssignments granted for a service principal” (updated October 15, 2024), https://learn.microsoft.com/en-us/graph/api/serviceprincipal-list-approleassignedto, accessed October 25, 2026.↩︎
Microsoft Learn, “List oAuth2PermissionGrants (delegated permission grants)” (updated July 11, 2024), https://learn.microsoft.com/en-us/graph/api/oauth2permissiongrant-list, accessed October 25, 2026.↩︎
Microsoft Learn, “List directoryAudits” (updated August 13, 2024), https://learn.microsoft.com/en-us/graph/api/directoryaudit-list, accessed October 25, 2026.↩︎
Microsoft Learn, “List signIns” (updated July 30, 2024), https://learn.microsoft.com/en-us/graph/api/signin-list, accessed October 25, 2026.↩︎
Microsoft Learn, “List signIns” (updated July 30, 2024), https://learn.microsoft.com/en-us/graph/api/signin-list, accessed October 25, 2026.↩︎
Microsoft Learn, “Microsoft Entra data retention” (updated January 6, 2026), https://learn.microsoft.com/en-us/entra/identity/monitoring-health/reference-reports-data-retention, accessed October 25, 2026.↩︎
Microsoft Learn, “userRegistrationDetails resource type” (updated July 22, 2024), https://learn.microsoft.com/en-us/graph/api/resources/userregistrationdetails, accessed October 25, 2026.↩︎
Microsoft Learn, “userRegistrationDetails resource type” (updated July 22, 2024), https://learn.microsoft.com/en-us/graph/api/resources/userregistrationdetails, accessed October 25, 2026.↩︎
Microsoft Learn, “List methods” (updated September 13, 2024), https://learn.microsoft.com/en-us/graph/api/authentication-list-methods, accessed October 25, 2026.↩︎
Microsoft Learn, “Get authenticationMethodsPolicy” (updated May 1, 2024), https://learn.microsoft.com/en-us/graph/api/authenticationmethodspolicy-get, accessed October 25, 2026.↩︎
Microsoft Learn, “Microsoft Entra feature availability” in Azure for US Government (updated January 15, 2026), https://learn.microsoft.com/en-us/entra/identity/authentication/feature-availability, accessed October 25, 2026.↩︎
Microsoft Learn, “Use My Staff to delegate user management,” how My Staff delegates common user tasks to managers through administrative units, https://learn.microsoft.com/entra/identity/role-based-access-control/my-staff-configure, accessed November 9, 2026. The availability itself is from the feature table cited above.↩︎
Microsoft Learn, “Compare Azure Government and global Azure,” section “Identity” (limitations with multifactor authentication: Trusted IPs isn’t supported; use Conditional Access policies with named locations), https://learn.microsoft.com/en-us/azure/azure-government/compare-azure-government-global-azure#identity, read November 1, 2026.↩︎