Training module · Draft
Module 1: Name one writer for every account state
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-08.
Part of the HR-Driven Identity learning path. Gate G1 Writers. Workbook Chapter 01. About 50 minutes.
Watch: the narrated deck for this module: Deck · ▶ Watch (about 13 minutes, captions) · MP4 · Script · Captions.
Introduction (3 minutes)
Your agency’s HR feed sets an account’s active value. A Lifecycle Workflows leaver can disable the same account. A help desk technician can still edit a user’s organization code. Each of those is a writer, and when two of them write the same value, the later one wins. A late active true from the pipeline undoes the workflow’s disable; a help desk edit moves a person’s access without HR knowing.
In this module you fix that before any other gate: you name one writer, and a backstop, for every account-state transition and HR-mastered value, and you test the directory audit log against that list.
The federal requirement. This gate supports NIST SP 800-53 AC-2 and IA-4 for the register, and AU-6 for reviewing writes outside the named writer. 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 requirements and the evidence are in the Gate 1 requirements table (under Federal requirements and evidence in Step 1) and in the workbook chapter’s Federal requirements and evidence.
| Requirement | KSI | Evidence |
|---|---|---|
| AC-2 and IA-4, the register | KSI-IAM-ELP, KSI-IAM-JIT | The signed config/writer-register.json and the check output |
| AU-6, writes outside the named writer | KSI-MLA-RVL | The scan’s findings.json, writes.json and queries.json |
| AU-9 and SI-7, evidence nobody edited | KSI-MLA-OSM; KSI-SVC-VRI | The scan folder’s manifest.json, a SHA-256 per file |
KSIs are FedRAMP 20x indicators for cloud service providers: the agency authorizes its own system and lines this evidence up with what its providers report against them, and it doesn’t report KSIs itself (Book 00, Appendix B). Each KSI lists the row’s control among its related SP 800-53 controls on FedRAMP’s Key Security Indicators page (read November 2, 2026); which file supports which indicator is the program’s mapping (design guidance). The workbook chapter’s table has a row for every lab step.
Learning objectives (1 minute)
After this module, you’ll be able to:
- explain why one writer per account state comes before every other gate;
- read the lifecycle rows of the writer register and say which writer and backstop each names;
- run the register check (WR-01 to WR-09) and the audit-log scan (WS-01, WS-02 and WS-04), and read their findings;
- say what the scan can’t see, and what covers it.
Prerequisites (1 minute)
- The learning path’s prerequisites.
- Python 3.10 or later and pytest, and a clone of the repository (What you need for the labs).
Unit 1: Why one writer comes first (8 minutes)
Read the guide’s Step 1. Its table of lifecycle rows is the register you’ll check in the lab: who creates the account, who enables it at hire, who disables it at separation, who deletes it, and who writes the hire date, the leave date, the manager and the certificate binding.
Then read why Book 03 treats a writer as an access decision:
- Book 03, Chapter 06, Writers: whoever can write a slot decides what a rule on it grants.
- Book 03, Chapter 04, Operating rules: the pipeline’s
activevalue and Lifecycle Workflows’ tasks can both write account state. - The workbook’s Why this gate comes first, which ties the two together.
As you read, notice the row for deletion: the post-separation workflow writes it, and inbound provisioning never does.
Unit 2: The register and the scan (6 minutes)
Read three sections of workbook Chapter 01:
- What it leaves to you: what Learn’s directoryAudits API gives you, and what you build yourself.
- The checks: the WR rules that check the register’s shape, and the WS rules that test each audit record against it.
- What the scan doesn’t see: writes made in Active Directory for synced users.
Keep the rule table open during the lab; you’ll match each finding to its rule.
Exercise: Lab 1, fixtures, no tenant (20 minutes)
This is the workbook’s Lab 1. Work in examples/hr-lifecycle. The audit records are fictional.
Every command in this lab, in order, from examples/hr-lifecycle. Each step below says what it does and what to look for.
# Step 1
python python/graph_export.py --task writers-scan --list
# Step 2
python python/writer_register.py check --register config/writer-register.json
# Step 3
python python/writer_register.py scan --register config/writer-register.json --audit tests/fixtures/ch01/directory-audits.json --since 2026-10-01 --until 2026-10-31 --cloud gcc --out out/ch01
# Step 4
python -c "import json; [print(r['Rule'], r['Severity'], r['Object'], '|', r['Issue']) for r in json.load(open('out/ch01/findings.json'))]"
# Step 5
python -c "import json; print(json.load(open('out/ch01/queries.json'))[2])"
# Step 6
python -m pytest tests -q
# Step 6
rm -rf outList what the scan reads, and with what permission:
python python/graph_export.py --task writers-scan --listTask writers-scan: AuditLog.Read.All, Directory.Read.All GET /auditLogs/directoryAudits [AuditLog.Read.All] GET /auditLogs/provisioning [AuditLog.Read.All, Directory.Read.All]Check the register:
python python/writer_register.py check --register config/writer-register.jsonExpect
Register: 0 Finding, 0 Warn, 0 Infoand exit code 0.Scan October’s audit records:
python python/writer_register.py scan --register config/writer-register.json --audit tests/fixtures/ch01/directory-audits.json --since 2026-10-01 --until 2026-10-31 --cloud gcc --out out/ch01Protected writes: 11 (writer 5, backstop 1, second writer 4, forbidden 1) Writers: 5 Finding, 1 Warn, 0 Info Written to out/ch01 (manifest.json lists every file's SHA-256)The exit code is 2, because the fixture holds findings.
Read the findings:
python -c "import json; [print(r['Rule'], r['Severity'], r['Object'], '|', r['Issue']) for r in json.load(open('out/ch01/findings.json'))]"Expect four WS-01 Findings (a help desk disable, an Exchange administrator’s leave-date edit, a help desk manager change and a fix-up script’s organization code), one WS-04 Finding (a delete by inbound provisioning, which the register forbids) and one WS-02 Warn (the backstop enabled a hire). The workbook lists the six lines verbatim.
Read one saved query:
python -c "import json; print(json.load(open('out/ch01/queries.json'))[2])"The third query is the
directoryAuditsfilter for thedisabled-at-separationrow:activityDisplayName eq 'Disable account'within the window, allowed forinbound-provisioningandlifecycle-workflows. Gate 7’s scheduled review runs these queries; Module 7’s lab reads the scan’s findings.Run the tests:
python -m pytest tests -qExpect
57 passed. Deleteoutwhen you’re done:rm -rf out
Exercise: Lab 2, your test tenant (5 minutes to read)
Not run by the authors. Follow the workbook’s Lab 2 in a worldwide or GCC test tenant, never production.
- What you do: make two known writes in the admin center, export the audit window, confirm the
modifiedPropertiesnames the register expects, put your tenant’s service principal IDs in a copy of the register, and scan. Both admin center changes should appear as WS-01. - Permissions: a reader app with a certificate, holding
AuditLog.Read.AllandDirectory.Read.Allas application permissions with admin consent, and nothing else. - Back-out: restore the test users’ values, remove the two permissions, delete the reader app and its certificate, and delete the export and output folders.
Lab 2 matters more here than in most modules: the property names in the register aren’t documented on Learn, and a wrong name makes the scan report silence that looks clean (Dangers).
Knowledge check (5 minutes)
- Your HR feed and the leaver workflow can both disable an account. What does the register record for the
disabled-at-separationrow?- Both, as equal writers, so either can act first
- One writer, with the other recorded as its backstop
- Neither; account state is left to the help desk
- Whichever wrote it most recently in the audit log
- The scan reports a WS-04 Finding for a deleted account. What happened?
- A writer the row forbids, inbound provisioning, deleted the account
- The backstop deleted the account before the writer did
- The delete failed and was retried
- The delete fell outside the scan’s window
- Which writes does the Entra audit-log scan not see?
- Writes by Lifecycle Workflows
- Writes by the HR provisioning app
- Writes made in Active Directory for synced users
- Writes by a help desk administrator in the admin center
- Why must Lab 2 confirm each
auditPropertyname against a real record before the scan counts as evidence?- Graph rejects a filter with an unknown property name
- Learn doesn’t document the names, and a wrong one makes the scan report nothing for that value
- The names change every 30 days
- The register check fails until the names are confirmed
- b. One writer per row, with a backstop that isn’t the writer itself (WR-01, WR-06). Taught in Step 1’s lifecycle rows and The checks.
- a. WS-04 flags a write by a writer the row forbids; the register forbids deletion by inbound provisioning. Taught in The checks.
- c. AD writes are seen only by AD change auditing; the guide’s interim path names the sync agent as a writer and reviews the OU ACLs. Taught in What the scan doesn’t see.
- b. The property names are the program’s reading, not Learn’s, and silence looks clean. Taught in Verified facts and Dangers.
Summary (2 minutes)
You checked a writer register’s shape, scanned a month of audit records against it, and read the saved query Gate 7 runs. You saw four second writers, one forbidden delete and one backstop that acted first.
The evidence an ISSO keeps for Gate 1: the signed register with its lifecycle rows and the versioned scope filter; the check output; the scan’s output folder with its manifest and the saved queries.json; and the AD auditing settings, which this code doesn’t read. The full list is in the workbook’s Federal requirements and evidence.