Training module · Draft
Module 1: Inventory every non-person identity
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-09.
Part of the Non-Person Identity learning path. Gate G1 Inventory. Workbook Chapter 01. About 55 minutes.
Watch: the narrated deck for this module: Deck · ▶ Watch (about 14 minutes, captions) · MP4 · Script · Captions.
Also: the chapter’s narrated tour, about 11 minutes: ▶ Watch.
Introduction (3 minutes)
Consent creates service principals, and Microsoft’s own services create more. So the inventory is always larger than the list of apps anyone asked for. No attribute marks an Active Directory user as a service account, either: Learn finds candidates by an SPN, trust for delegation and a password that never expires, and warns that this might not find them all.
In this module you read every non-person identity into one register, with its type, owners, credentials, risk and candidate target. You decide nothing yet. Inventory first; decide later.
The federal requirement. Gate G1 supports NIST SP 800-53 AC-2, IA-4 and CM-8. The gate’s output is evidence for these controls; your organization still sets their parameters, policy and procedures, and the assessor decides whether each is satisfied. The guide’s Validate (G1 Inventory) step names them, and the workbook chapter’s Federal requirements and evidence table has a row for every lab step.
| Requirement | KSI | Evidence |
|---|---|---|
| CM-8 System Component Inventory | KSI-PIY-GIV | register.json and register.csv; data-platform-handoff.csv |
| AC-2 Account Management; IA-4 Identifier Management | KSI-IAM-SNU (AC-2); KSI-IAM-ELP, KSI-IAM-JIT (IA-4) | register.csv columns IdentityId, Type, OwnerCount, OwnerIds |
| AC-6(5) Privileged Accounts; IA-5 Authenticator Management | KSI-IAM-SNU, KSI-IAM-JIT | findings.json, NPI-INV-01 (HIGH risk) |
| AC-6 Least Privilege, for the reading process | KSI-IAM-ELP, KSI-IAM-JIT | Get-NpiRequiredPermission -Task inventory; config/calls.json |
| AU-9 and SI-7, the run’s integrity | KSI-MLA-OSM (AU-9); KSI-SVC-VRI (SI-7) | manifest.json: tenant, every request, a SHA-256 per file |
KSIs are FedRAMP 20x indicators for cloud service providers. The agency lines this evidence up with what its providers report against them; it doesn’t report KSIs itself. Which file supports which indicator is the program’s mapping (design guidance). The guide’s Evidence section explains both.
Learning objectives (1 minute)
After this module, you’ll be able to:
- name the identity types the register holds, and where each is read from;
- explain the program’s HIGH, MEDIUM and LOW risk rule, and why a stale account is only a lead;
- run the inventory against the fixtures and read its HIGH-risk findings;
- show that the reader refuses a call the task doesn’t declare and a cloud it doesn’t know.
Prerequisites (1 minute)
- The guide’s track at a glance: the system inventory or CMDB, not HR, is this track’s source of truth.
- Microsoft Automation, Module 1: permissions.
Unit 1: What the gate asks for (8 minutes)
Read the guide’s Step 1: six sub-steps, from AD service accounts (1a) to joining each identity to its record (1f). Then read:
- The guide’s record each identity joins to: the fields every record carries.
- The guide’s validation kit table: G1 is proved by VK-NPI-01, 06, 07 and 08.
Unit 2: The register as code (12 minutes)
Read the workbook chapter in this order:
- What the register has to hold: two AD collections, nine Graph collections and one Resource Manager one.
- The reader: its five rules, and why
Directory.Read.Allis held only to list delegated grants. - Risk, targets, stale leads and the hand-off: the program’s rule (design guidance).
- The findings: NPI-INV-01 to -09.
Exercise: Lab 1, fixtures, no tenant (20 minutes)
This is the workbook’s Chapter 01, Lab 1, which explains each result. Work in examples/non-person-identity, in PowerShell 7. The estate is fictional.
Every command in this lab, in order, from examples/non-person-identity. Each step below says what it does and what to look for.
# Step 1: Run Learn's samples against the stand-ins
Invoke-Pester -Path tests/MicrosoftSamples.Tests.ps1 -FullNameFilter '*Chapter 01*' -Output None -PassThru |
Select-Object PassedCount, FailedCount, NotRunCount
# Step 2: List what the inventory needs
Import-Module ./powershell/NpiInventory/NpiInventory.psm1
$p = Get-NpiRequiredPermission -Task inventory
$p.GraphPermissions -join ', '; $p.AzureRoles -join ', '; $p.AdRights -join ', '
# Step 3: Run the inventory against the fictional estate
./Invoke-NpiInventory.ps1 -Cloud gcc -FixturePath tests/fixtures/estate.json -OutputPath out/inventory
# Step 4: Read the HIGH-risk identities
Get-Content out/inventory/findings.json -Raw | ConvertFrom-Json | Where-Object Rule -eq 'NPI-INV-01' | Format-Table Object, Issue -Wrap
# Step 5: See the reader refuse
$r = New-NpiReader -Cloud gcc -Task inventory -Invoke (New-NpiFixtureInvoker -Path tests/fixtures/estate.json).Invoke
Invoke-NpiList -Reader $r -CallId graph-user -Bind @{ id = 'x' }
Get-NpiCloud GCC
# Step 6: Read the hand-off to the Data Platform plan
Import-Csv out/inventory/data-platform-handoff.csv | Format-Table Name, Type, Spns, Risk
# Step 7: Run the tests
Invoke-Pester -Path tests
# After the steps
Remove-Item -Recurse -Force outRun Learn’s samples against the stand-ins:
Invoke-Pester -Path tests/MicrosoftSamples.Tests.ps1 -FullNameFilter '*Chapter 01*' -Output None -PassThru | Select-Object PassedCount, FailedCount, NotRunCountExpect
PassedCount 20,FailedCount 0,NotRunCount 10. The ten not run are Chapter 02’s.List what the inventory needs:
Import-Module ./powershell/NpiInventory/NpiInventory.psm1 $p = Get-NpiRequiredPermission -Task inventory $p.GraphPermissions -join ', '; $p.AzureRoles -join ', '; $p.AdRights -join ', 'Application.Read.All, Directory.Read.All Reader Read (default for authenticated domain users)Nothing ends in
ReadWrite.Run the inventory against the fictional estate:
./Invoke-NpiInventory.ps1 -Cloud gcc -FixturePath tests/fixtures/estate.json -OutputPath out/inventoryRegister: 35 identities (AD 15, Entra 20); risk HIGH 8, LOW 19, MEDIUM 8 Findings: 8 Finding, 11 Warn, 3 Info; 3 handed to the Data Platform plan Written to out/inventory (manifest.json: tenant c0ffee00-0000-4000-8000-000000000001, every request, a SHA-256 per file)Read the HIGH-risk identities:
Get-Content out/inventory/findings.json -Raw | ConvertFrom-Json | Where-Object Rule -eq 'NPI-INV-01' | Format-Table Object, Issue -WrapExpect eight rows:
svc-hrsync,svc-sqlprod,svc-backup,svc-oldftp,jdoe,svc-webfront,gmsa-ops$and the Mail Automation app registration.jdoewas found only by a password that never expires, so it may be a person.See the reader refuse:
$r = New-NpiReader -Cloud gcc -Task inventory -Invoke (New-NpiFixtureInvoker -Path tests/fixtures/estate.json).Invoke Invoke-NpiList -Reader $r -CallId graph-user -Bind @{ id = 'x' } Get-NpiCloud GCCExpect two errors:
Undeclared call 'graph-user' for task 'inventory'; add it to config/calls.json with its permission.andUnknown cloud 'GCC'. Use global, gcc, gcchigh or dod.Read the hand-off to the Data Platform plan:
Import-Csv out/inventory/data-platform-handoff.csv | Format-Table Name, Type, Spns, RiskExpect
svc-sqlprodandgmsa-sql$, by theirMSSQLSvc/SPNs, andsvc-ldapbind, by name.Run the tests:
Invoke-Pester -Path testsExpect 513 tests passed, 0 failed.
Delete out when you’re done:
Remove-Item -Recurse -Force outExercise: Lab 2, your test tenant (5 minutes to read)
Not run by the authors. Follow the workbook’s Chapter 01, Lab 2. It has the exact commands.
- You need: a test tenant in the worldwide or GCC cloud with a test Azure subscription, and a test Active Directory domain, never production, with fictional people.
- What you do: register a reader app with a certificate, grant it
Application.Read.All,Directory.Read.Alland Reader on one resource group, run the inventory live, and compare the register with the admin center’s lists. - Back-out: step 8 removes the Reader assignment and the two permissions, deletes the app registration and its certificate, and deletes
out/inventory-test.
Knowledge check (5 minutes)
- Why does the inventory hold
Directory.Read.All?- To read users’ passwords
- Because Learn’s least-privileged permission for listing delegated permission grants is
Directory.Read.All - To read service principal sign-in activity
- Because
Application.Read.Allcan’t list app registrations
- An enabled AD account has had no logon for 90 days. What does the code do?
- Disables it
- Moves it to a holding OU
- Raises a stale lead (NPI-INV-02) to confirm against domain controller and Entra sign-in logs, and changes nothing
- Deletes it after 30 days
jdoeis HIGH risk and also carries NPI-INV-08. Why does that matter?- It was found only by a password that never expires, so it may be a person, not a service account
- It’s a gMSA
- It holds a client secret
- It belongs to another tenant
- Which accounts go to the Data Platform plan?
- Every gMSA
- Accounts with an
MSSQLSvc/SPN, or names that match the program’s SQL Server and LDAP-bind patterns - Every HIGH-risk account
- Managed identities
- Which KSI does the guide’s evidence table align with CM-8 System Component Inventory?
- KSI-IAM-JIT
- KSI-PIY-GIV
- KSI-MLA-EVC
- KSI-SVC-ASM
- b. It’s the broadest read the inventory holds, and the only reason it holds it. Taught in The reader.
- c. A stale account is a lead only, and the code never disables, moves or deletes anything. Taught in Risk, targets, stale leads and the hand-off.
- a. Learn’s criteria can find people too. Taught in The findings and the chapter’s Dangers.
- b. The plan’s task NP0.4. Taught in Risk, targets, stale leads and the hand-off.
- b. Taught in the guide’s Evidence table.
Summary (2 minutes)
You read a fictional estate of 35 identities into one register, found the eight HIGH-risk ones, saw the reader refuse an undeclared call, and handed three SQL Server and LDAP-bind accounts to the Data Platform plan.
The evidence an ISSO keeps for G1 from this module: register.json, register.csv, findings.json, data-platform-handoff.csv and the manifest.json that hashes them, dated; the permission list from step 2; and the test result. Before shipping, work through the chapter’s Checklist.