Create adr-004-external-auth-user-provisioning-policy.md#8383
Create adr-004-external-auth-user-provisioning-policy.md#8383ejaronne wants to merge 11 commits into
Conversation
|
REGISTRATION_DISABLED=false, true, local, SSO |
ADR-004 Review — verification, industry comparison, and recommended revisionsReviewed against the heimdall2 source ( 1. Verification findings (against actual source)
The LDAP finding settles review questions 1 and 2 together: the gate covers LDAP automatically — and therefore the variable name 2. Industry comparison
Every product surveyed agrees with this ADR's core call: local registration and external-auth JIT are separate controls (rejecting Option A is industry-consistent). The gap: GitLab and SonarQube show two patterns this ADR doesn't consider — the approval queue and the mode enum. Sources: GitLab OmniAuth · Grafana auth · SonarQube provisioning · Harbor OIDC 3. Recommended design changes3a. Mode enum instead of boolean — GitLab's lesson is that booleans accrete into a config maze. One enum absorbs the future without migration: EXTERNAL_AUTH_USER_PROVISIONING=jit | approval | disabled # default: jit
Fail-fast on unknown values at startup. Note the current boolean pattern silently treats a typo ( 3b. State the choke point as an architectural invariant. All six strategies (google, github, gitlab, okta, oidc, ldap) funnel through async provisionExternalUser(identity: ExternalIdentity): Promise<User> {
const existing = await this.findByEmail(identity.email);
if (existing) return this.refreshMetadata(existing, identity);
switch (this.config.externalAuthProvisioning()) { // exhaustive
case 'jit': return this.createActiveUser(identity);
case 'approval': return this.createPendingUser(identity); // phase 2
case 'disabled': throw new AccountNotProvisionedError();
}
}3c. Promote audit logging from review question (§11 Q3) to in-scope AC. §1.3's justification is "auditors expect account creation to be traceable" — the rejection event IS the audit artifact this feature exists to produce. Structured events at the choke point: 3d. Specify the frontend error contract now (§11 Q4, §9.3 risk). 3e. Add the misconfiguration advisory — app + docs + defaults. The ADR's own §9.2 negative ("admins can set
4. UX sketchesLogin modal — rejected ( Admin panel — persistent configuration advisory (post-login, dismissible): Admin ▸ Users — approval queue (phase 2 payoff: default-deny with one-click approvals, no manual email matching): 5. Migration story this enables
6. Direct answers to §11 review questions
Smaller items
|
Updated ADR scope based on reviews 7/9/2026
Re-review of the revised ADR — endorsed, with answers to §12 and a set of editsThe Answers to the §12 review questions, plus edits being applied directly to the ADR in a follow-up commit: Q1 — unknown values: fail fast. The enum makes permissive-on-unknown more dangerous than before: Q2 — yes, it's a breaking change, and release notes are the weakest channel. The cohort that breaks ( Q3 — split it. Admin user creation while Also restored: the frontend rendering contract. The revision keeps the Two hygiene edits:
Edits incoming on this branch. |
…n-page rendering contract, Option E, LDAP matrix rows Resolves review questions 1-2: unknown REGISTRATION_DISABLED values refuse to start (enum typo must not silently open both account-creation paths); breaking change promoted to required deliverable with three communication channels (release note, boot warning targeting the affected cohort, login alert). Restores the frontend rendering contract for account_not_provisioned. Adds Option E (approval queue) as rejected-with-structural-note, explicit LDAP rows in the behavior matrix, and work-order phases 5-6 for the new surfaces. Authored by: Aaron Lippold<lippold@gmail.com> Signed-off-by: Aaron Lippold <lippold@gmail.com>
…aking cohorts, provisioning gates, test matrix - 3.5/3.6: specify the REAL error transports (authenticationErrorCode cookie for OAuth callbacks, 401 JSON body for LDAP/local POST — the redirect query-parameter mechanism described previously does not exist), alert precedence over the generic snackbars, filter log redaction - 3.3: structured error code on the exception, NotFoundException-only catch, missing-email guard (external_identity_missing_email), LDAP await fix and multi-valued-mail binding rules - 6.2: second breaking cohort (formerly-permissive invalid values now refuse to boot) and the upgrade-ordering/rollback rule for local/sso - 6.3: per-strategy verified-email reality (GitHub/Google check, OIDC bypassable via OIDC_USES_VERIFIED_EMAIL=false, GitLab/Okta/LDAP do not) plus in-scope mitigations - 8.2: LOCAL_LOGIN_DISABLED=true blocks admin user creation too — provision before disabling; LDAP first-value email note - 3.7: enabled-strategy predicate defined; warning stated as a permanent configuration-shape notice - 3.1: trim before validate; 7.1 whitespace/legacy-value rows and per-family warning tests; 7.2 non-NotFound rethrow + missing-email tests; new 7.5 exception-filter and 7.6 frontend test sections; 7.4 actual test-surface facts and required automated deny-path e2e - 3.2/13: frontendStartupSettings registrationEnabled surface documented; 3.4 manifest example quoted as a string; 9.3 risks grounded; work-order phases 4-9 updated to match; header credits both authors Findings verified by independent adversarial review against the worktree source before application. Authored by: Aaron Lippold<lippold@gmail.com> Signed-off-by: Aaron Lippold <lippold@gmail.com>
…ormalization, NIST mapping, precedents Findings from a verified web-research pass (every citation re-fetched against primary sources): - 2/5/6.3: name the sso self-provisioning bypass precisely — the Classic-Federated Merge attack (Sudhodanan & Paverd, USENIX Security 2022 Sec 4.1); sso prevents silent JIT, not admin-gated provisioning; new behavior-matrix row; email-ownership verification carded as open question 12.4 - 3.3: trim+lowercase external emails before lookup/create (findByEmail is case-sensitive, no normalization exists anywhere in the users module); 8.2 operator rule; 7.2 test 14 - 6.1: NIST 800-53 rev 5 mapping (AC-2a/e/f/i, AC-2(1), IA-4a/d) and 800-63C-4 Sec 4.6.3 provisioning-model framing for ATO documentation - 6.3: email is the wrong long-term binding key — OIDC Core 5.7 (sub+iss only stable identifier), ASVS 5.0 V10.5.2, 800-63C-4 3.4/2.3/3.8.1; iss+sub identity table named as the future ADR; no user-disable capability documented as an AC-2f gap - 3.5: audit event JSON shape pinned (winston loggers have no structured convention to inherit) - 3.6: alert names the attempted provider (GitLab precedent); ASVS V6.3.8 enumeration reasoning recorded; Keycloak generic-failure anti-pattern cited - 3.1/3.7/6.2: precedents cited — PostgreSQL password_encryption boolean-to-enum + v14 strictness, Django 4.0 CSRF system check, Grafana in-product warnings, GitLab upgrade-notes process; permanent warning noted as a deliberate extension beyond precedent - 2: default-JIT-on rationale recorded (Gitea/Forgejo default off; GitLab/Grafana default on) - 4: Option C gains per-provider precedents + Mattermost choke-point cautionary tale; Option E gains blocked_pending_approval specifics and a composition rule for future provisioning variables - 3.4/9/11: mitre/saf-packaging backend.env surface; 13: standards reference list Authored by: Aaron Lippold<lippold@gmail.com> Signed-off-by: Aaron Lippold <lippold@gmail.com>
… source Replace inferred framework behavior with facts verified against the exact versions this repo resolves (@nestjs/common@11.1.28, @nestjs/passport@11.0.5 per yarn.lock) and the current NestJS exception-filter docs: - 3.3: object-form UnauthorizedException replaces the ENTIRE 401 body verbatim (HttpException.createBody returns the object as-is), so the snippets now include statusCode explicitly; exception.message still resolves to the message field, keeping existing filter/log code working - 3.5: exact wire body documented for the POST transport; the filter extracts the code via exception.getResponse().error (verified public API) - 3.3: LDAP un-awaited promise mechanism made precise — auth.guard.js handleRequest rethrows the original strategy error, while the un-awaited case survives only via promise adoption at canActivate's await; the await-fix routes rejection through the verified rethrow path Authored by: Aaron Lippold<lippold@gmail.com> Signed-off-by: Aaron Lippold <lippold@gmail.com>
ADR-005 proposes the mitre/vulcan docs pattern for heimdall2: self-contained docs/ (own package.json/yarn.lock, VitePress 2 + Vue 3) isolated from the Vue 2 app by workspace-glob exclusion — root package.json/lerna.json never change; GitHub Pages deploy workflow; migration of all 24 wiki content pages plus repo Markdown and ADRs into a published decisions/ section; wiki reduced to pointer stubs. Alternatives: status quo, plain markdown, wiki-sync action, MkDocs, Docusaurus. Six implementation phases. ADR-004: 3.4 and References note the wiki is the channel today and ADR-005 supersedes it when implemented. Authored by: Aaron Lippold<lippold@gmail.com> Signed-off-by: Aaron Lippold <lippold@gmail.com>
Epic heimdall2-4qg (10 cards, ADR-004 phases 1-10) and epic heimdall2-yvx (6 cards, ADR-005 phases 1-6) now referenced from each ADR's work-order section; card IDs mirror phase numbers. Cross-epic soft reference on Phase 9 (wiki path is the fallback until the docs site lands). Authored by: Aaron Lippold<lippold@gmail.com> Signed-off-by: Aaron Lippold <lippold@gmail.com>
…abilities §2.3.1 file-level reference tree (every wiki page's destination, NEW pages marked: production-checklist, decisions index, release-notes section), §2.3.2 landing hero/features/nav spec, §2.3.3 local search + canonical env-vars page rule + CONTRIBUTING.md gap noted as an owner decision. Authored by: Aaron Lippold<lippold@gmail.com> Signed-off-by: Aaron Lippold <lippold@gmail.com>
…ing links Both work-order sections now tell the team how to pull the board (bd from gastownhall/beads, bd dolt pull / bd bootstrap), carry the v49->v54 pull-before-upgrading-bd migration note, and link mitre/mitre-saf-skills for the card/TDD/AC-verify workflow. Authored by: Aaron Lippold<lippold@gmail.com> Signed-off-by: Aaron Lippold <lippold@gmail.com>
|
Deep review + research pass applied — seven commits on this branchFollowing up on my earlier endorsement: I ran a full verification pass over the revised ADR (every finding independently re-checked against the code before being applied), plus a targeted research pass with every citation verified against primary sources. Net result: the enum design held up across all of it — nothing overturned the core decision. What changed is precision, evidence, and scope-honesty. Highlights worth your eyes: The two things that were blocking
Other substantive additions
ADR-005 + work tracking
Eugene — the seven commits are individually scoped if you want to review pass-by-pass; happy to walk through any finding. If anything reads as over-reach, flag it and we'll cut it back. |



https://github.com/mitre/heimdall2/blob/external_auth_provisioning_policy/docs/adr-004-external-auth-user-provisioning-policy.md