Skip to main content
This is not an official U.S. federal government website. The Federal Modernization Workbook is independent technical analysis. It does not represent the position of, and has not been reviewed or endorsed by, any federal agency.

Training module · Draft

Module 1: Inventory every non-person identity

Gate G1 and workbook Chapter 01: read every AD service account, MSA, gMSA, dMSA, app registration, service principal and managed identity into one register with a risk and a candidate target, see the reader refuse a call it doesn’t declare, and hand SQL Server accounts to the Data Platform plan. About 55 minutes, with a lab on fixtures and a test tenant lab.

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)

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:

Unit 2: The register as code (12 minutes)

Read the workbook chapter in this order:

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.

NoteEvery command in Lab 1

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 out
  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

    Expect PassedCount 20, FailedCount 0, NotRunCount 10. The ten not run are Chapter 02’s.

  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 ', '
    Application.Read.All, Directory.Read.All
    Reader
    Read (default for authenticated domain users)

    Nothing ends in ReadWrite.

  3. Run the inventory against the fictional estate:

    ./Invoke-NpiInventory.ps1 -Cloud gcc -FixturePath tests/fixtures/estate.json -OutputPath out/inventory
    Register: 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)
  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

    Expect eight rows: svc-hrsync, svc-sqlprod, svc-backup, svc-oldftp, jdoe, svc-webfront, gmsa-ops$ and the Mail Automation app registration. jdoe was found only by a password that never expires, so it may be a person.

  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

    Expect two errors: Undeclared call 'graph-user' for task 'inventory'; add it to config/calls.json with its permission. and Unknown cloud 'GCC'. Use global, gcc, gcchigh or dod.

  6. Read the hand-off to the Data Platform plan:

    Import-Csv out/inventory/data-platform-handoff.csv | Format-Table Name, Type, Spns, Risk

    Expect svc-sqlprod and gmsa-sql$, by their MSSQLSvc/ SPNs, and svc-ldapbind, by name.

  7. Run the tests:

    Invoke-Pester -Path tests

    Expect 513 tests passed, 0 failed.

Delete out when you’re done:

Remove-Item -Recurse -Force out

Exercise: 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.All and 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)

  1. Why does the inventory hold Directory.Read.All?
    1. To read users’ passwords
    2. Because Learn’s least-privileged permission for listing delegated permission grants is Directory.Read.All
    3. To read service principal sign-in activity
    4. Because Application.Read.All can’t list app registrations
  2. An enabled AD account has had no logon for 90 days. What does the code do?
    1. Disables it
    2. Moves it to a holding OU
    3. Raises a stale lead (NPI-INV-02) to confirm against domain controller and Entra sign-in logs, and changes nothing
    4. Deletes it after 30 days
  3. jdoe is HIGH risk and also carries NPI-INV-08. Why does that matter?
    1. It was found only by a password that never expires, so it may be a person, not a service account
    2. It’s a gMSA
    3. It holds a client secret
    4. It belongs to another tenant
  4. Which accounts go to the Data Platform plan?
    1. Every gMSA
    2. Accounts with an MSSQLSvc/ SPN, or names that match the program’s SQL Server and LDAP-bind patterns
    3. Every HIGH-risk account
    4. Managed identities
  5. Which KSI does the guide’s evidence table align with CM-8 System Component Inventory?
    1. KSI-IAM-JIT
    2. KSI-PIY-GIV
    3. KSI-MLA-EVC
    4. KSI-SVC-ASM
  1. b. It’s the broadest read the inventory holds, and the only reason it holds it. Taught in The reader.
  2. 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.
  3. a. Learn’s criteria can find people too. Taught in The findings and the chapter’s Dangers.
  4. b. The plan’s task NP0.4. Taught in Risk, targets, stale leads and the hand-off.
  5. 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.

Next: Module 2: Owners, sponsors and the orphan rule.

Back to top