About this document. This guide documents the current state of IAG as actually verified — not as designed. Every screenshot is a real capture from the running application, and every claim marked [V] was validated live in the session dated 2026-08-26. The validation evidence is summarized in Appendix A.
Table of Contents
- What IAG does
- Architecture at a glance
- Getting started
- The admin interface — view by view
- 4.1 Dashboard
- 4.2 Identities
- 4.3 Sources
- 4.4 Entitlements
- 4.5 Campaigns (list, detail, report)
- 4.6 Risk
- 4.7 SoD Rules
- 4.8 Remediation (incl. SCIM provisioning)
- 4.9 API Keys
- 4.10 Users
- 4.11 Reminders (outbox)
- 4.12 Reviews
- 4.13 Audit
- 4.14 Theming
- Roles and permissions
- Connectors and sync
- SCIM provisioning and enforcement
- Operational runbook
- Troubleshooting
- Appendix A — Validation evidence (2026-08-26)
- Where to read more
1. What IAG does
IAG (Identity & Access Governance) is a self-hosted application that:- Ingests identities and access from CSV uploads or live connectors (LDAP/AD, Microsoft Entra ID, SQL, CSV-on-URL) into a stable entitlement catalog. Re-syncs refresh; they never duplicate.
- Runs certification campaigns scoped by source, department, privilege, or orphaned accounts. Reviewers approve or revoke; campaigns auto-complete at 100% decided.
- Detects toxic combinations (segregation of duties) in campaign previews and review details.
- Remediates and enforces — decision-triggered rules notify owners by email, call webhooks, or write access changes back to the source (LDAP member removal, Entra group writes, SQL statements).
- Provisions via SCIM 2.0 into a SCIM target, managed from the admin UI.
- Scores risk from unreviewed-access age and privilege weighting.
- Keeps a tamper-evident audit trail — append-only, hash-chained, written in the same transaction as every state change, with a chain-verification endpoint and a pull-only JSONL SIEM feed.
- Issues API keys (bearer-token, role-scoped) alongside session login.
2. Architecture at a glance
The resilience contract [V]: Postgres is the only durable state; replicas
are stateless (signed JWT sessions — any replica validates any session); only
the migrate container touches schema; if all replicas are down, nginx returns
502 and nothing writes; the audit chain is append-only and hash-chained; and
fault tolerance is tested, not assumed —
scripts/smoke.sh kills a replica
mid-service and expects continued service plus a still-valid chain. This was
re-verified live (see Appendix A).
Optional connectors compose profile adds glauth (read-only LDAP for sync
proofs) and openldap (writable LDAP for enforcement write-back proofs).
3. Getting started
Requirements: Docker (Engine/Desktop with Compose v2) and Python for the secret generator. Nothing else — the image builds frontend and backend.app.bootstrap creates the admin identity (employee_id E-ADMIN) and its
system_admin login. Subsequent boots skip bootstrap (“N users exist;
skipping”) [V].

up beside the
original — it would adopt/recreate the live project. Use the tested overlay
(see Runbook §8.4) to run as project
iag-test, containers iag-test-*, on port 8091 with its own volumes
and network. The original stack keeps running untouched [V].
4. The admin interface — view by view
All screenshots below are live captures from v0.3.0 with real proof data loaded (identities, sources, connectors, campaigns, remediation actions, e-mails, audit history). Navigation is the header bar; the theme toggle sits at its right end.4.1 Dashboard
Portfolio counters and your personal workload at a glance.
4.2 Identities
The people directory: employee IDs, usernames, e-mail, department, manager, active flag. Create/update via modals; CSV import upserts; CSV export streams. A manager-cycle guard rejects circular reporting lines.
4.3 Sources
Data sources (CSV upload, LDAP, Entra ID, SQL, CSV-on-URL) and their accounts. Create-source is a modal with owner autocomplete (typeahead over real identities — the owner field takes an employee ID). Connector configuration, manual Sync now, sync history, and account linking (single or bulk by username/e-mail) all live here.
Note: connector sync creates accounts and the entitlement catalog, and deliberately leavesaccount.entitlement_id/account.identity_idNULL — linking access to identities is a separate, explicit step (bulk-link or CSV import). This is by design; the “Unlinked accounts” counter surfaces the work remaining. [V]
4.4 Entitlements
The normalized access catalog with privilege levels. Stats header; privilege changes are audited.
4.5 Campaigns
Certification campaigns: scope by source/department/privilege/orphans, Preview (dry-run reviewer resolution — see exactly which reviews a start would create and why any are skipped), then Stage → Start. Starting regenerates reviews and enqueues reminder e-mails. Campaigns auto-complete at 100% decided.


4.6 Risk
Unreviewed-access age × privilege-weighted risk per identity, with reports. Risk windows are configured viaIAG_RISK_UNREVIEWED_DAYS.

4.7 SoD Rules
Segregation-of-duties rules: toxic entitlement combinations. Violations surface in campaign previews and review details (flagged rows), so reviewers see the risk before deciding.
4.8 Remediation
Decision-triggered rules with four action types —notify_owner e-mail,
webhook, and enforce (directory write-back: LDAP member removal / Entra
group writes / SQL statements). Rules filter by privilege level and
entitlement pattern; approval gating is per-rule (on by default — leave it on
until you trust a rule). The action queue shows each action’s lifecycle
(pending_approval → completed/failed) with approve/cancel/retry.
The SCIM provisioning card also lives here — see
§7.

pending_approval; a webhook rule
delivered to a live sink; every state change appended to the audit chain.
4.9 API Keys
Bearer-token machine access. Keys are shown once at creation (only a hash is stored); role-scoped; read-only chokes apply per role; revocation is immediate and audited.keys-manage-keys is blocked (a key cannot manage
keys).

4.10 Users
In-app user administration (system_admin): create users against identities, assign roles, activate/deactivate, trigger password resets. Login lockout afterIAG_MAX_LOGIN_ATTEMPTS failed attempts for
IAG_LOCKOUT_DURATION_MINUTES.

4.11 Reminders (outbox)
The review-reminder e-mail queue with retry/dead-letter. Reminder cadence and stuck-row reclaim are env-tunable; delivery is adaptive SMTP (STARTTLS when offered, AUTH when offered) or log-only dev delivery when no SMTP host is set.
4.12 Reviews
The reviewer’s queue: approve/revoke per review (revocation requires a comment), bulk decisions, and history. Campaigns auto-complete on the last decision.
4.13 Audit
The append-only, hash-chained audit log: paginated, filterable, CSV-export, and a live chain verification badge.record_hash = SHA256(prev_hash + canonical_json(entry)); the verify endpoint walks the full chain — it read
Valid · 177 entries on the validated instance [V]. SIEM consumers
pull the JSONL feed (/api/audit/feed) with Last-Id pagination and the
advertised chain head.

4.14 Theming
Light/dark themes via the header toggle (button.theme-toggle); the choice
persists. Both were walked live [V].

5. Roles and permissions
Sessions are signed stateless JWTs (httpOnly cookie); API keys present a
Bearer principal with the same role chokes.
6. Connectors and sync
- CSV upload — natural-key entitlement upsert; simplest path to a populated catalog.
- LDAP/AD — bind + search validated live at config save (PUT runs a real probe); sync normalizes posixAccount users and groups.
- Entra ID — group writes for enforcement; no local tenant is required for sync-only use.
- SQL — admin-supplied query; validation runs
LIMIT 1for real; secrets are stored separately from the URL ($SECRETplaceholder). - CSV-on-URL — scheduled re-pull.
IAG_CONNECTOR_POLL_SECONDS; stuck runs are reclaimed after
IAG_CONNECTOR_STUCK_MINUTES. Manual sync is available per source
(Sync now).
7. SCIM provisioning and enforcement
(Condensed from the original SCIM guide — see git history for the long form andSPECS/feature-6-scim-provisioning-enforcement.md for design.)
Provisioning — off until enabled + token generated (endpoints answer 503
until then; audits record nothing). Point your IdP at
/api/scim/v2/Users with Authorization: Bearer <token>. Supported:
create/list(+filters)/PATCH(incl. active)/DELETE per RFC 7644. The IdP’s
externalId is the join key — pick a stable one (UPN/employee number), or
records split. The token shows once; rotation revokes instantly.
Enforcement rules — action enforce with target remove_entitlement
(entitlement name must match the directory group name) or disable_account.
Approval on by default. CSV/spreadsheet sources have no write-back — their
actions fail loudly and requeue, never silently skip. After an approved
action completes, Sync now closes the drift window. Already-clean
directories complete as already clean without writing.
8. Operational runbook
8.1 Health & chain checks
8.2 Backend test suite
8.3 Fault-tolerance smoke
bash scripts/smoke.sh — kills iag-app-2, expects service + chain to hold,
restarts and waits for healthy. For an isolated (renamed) stack use
scripts/smoke.test.sh with BASE_URL/IAG_APP2_CONTAINER exported.
8.4 Running an isolated clone (validated workflow)
8.5 Live-proof battery
All proofs honor env overrides. The full set (13 scripts) with isolation:reminder first (expects a solo campaign);
risk → report → siem in that order (siem needs ≥50 accumulated audit
entries, so run it late); enforce/remediation late; smtp last (its
campaign pollutes outbox counts). report and remediation are
order-sensitive — on a shared DB they may need solo runs on a fresh volume.
live_remediation_check additionally requires SMTP env (SINK_LOG) and, on
fresh databases, performs its own entitlement/identity binds (added 2026-08-26).
8.6 Reset to factory
9. Troubleshooting
All replicas crash-loop at boot:IAG_SMTP_HOST set but missing: IAG_SMTP_USER, IAG_SMTP_PASSWORD — fail-fast settings validation. Setting
any SMTP host requires user+password, even for a local sink that offers no
AUTH. Set both (dummy values are fine for a no-AUTH sink) and recreate.
Consider upstream: warn instead of crash, or document on the knob. [V]
Compose from a clone touched the wrong stack — the compose file pins
project name iag, container names, and port 8090. Without the overlay
(§8.4), docker compose up in a clone adopts the live project. Always use
-f compose.yaml -f compose.test.yaml in clones.
bash scripts/... behaves oddly / env vars missing on Windows — the
available bash may be WSL, which does not inherit arbitrary Windows env
vars (only WSLENV-listed ones) and uses /mnt/d/... paths. Export inside
bash: bash -c "export BASE_URL=...; cd /mnt/d/iag-test && bash scripts/smoke.test.sh".
.env sourcing fails under bash ($'\r': command not found) — CRLF
line endings. Normalize: (Get-Content .env -Raw) -replace "\r`n”,“`n” | Set-Content -NoNewline .env`.
Live proof “no reviews created” — campaign review generation skips
accounts with identity_id IS NULL (and source_owner mode needs a source
owner resolvable to a login). Link synced accounts (bulk-link) before
campaigning. [V]
live_remediation_check failing on fresh DBs — historical drift: the
script posted owner_identity_id where the API takes owner_employee_id
(silently dropped by Pydantic), and predated the sync-leaves-links-NULL
design. Fixed in the validation branch (contract fix + house-style binds).
iag-openldap exited(1) — the optional writable-LDAP proof container
(from the connectors profile). Not part of the default stack; restart with
--profile connectors up -d if running enforcement proofs.
SIEM walker sees fewer entries than expected — entries accumulate with
every proof; the check requires ≥50. Run it after other proofs (§8.5).
Blank screenshots via browserbase-local (Stagehand) — the local
Stagehand screenshot path produced byte-identical blank frames (4 KB) while
the DOM was live. The Playwright browser stack captured correctly; fall back
to it for headless capture. [V]
10. Appendix A — Validation evidence (2026-08-26)
Environment: isolated cloneD:\iag-test (branch validation/test-isolation,
commit c398464 + this guide), stack iag-test on port 8091, original
production stack untouched throughout.
Fixes contributed during validation (cherry-pickable): env-overridable
container names in six live scripts;
live_remediation_check contract fix +
binds; smoke.test.sh; this guide.
11. Where to read more
README.md— getting runningREQUIREMENTS.md— domain rules, roles, invariants (the contract)ARCHITECTURE.md— stack, topology, resilience contractSPECS/— per-feature design specs (connectors, remediation, API keys, risk/reports/SIEM, SCIM/enforcement, RBAC/themes)docs/polish-pass-2.md— UI component and theming decisionsCHANGELOG.md/HANDOFF.md— history and continuation notes