ADR-0023: no ordering of M4/M5/M6 preserves decision 4 — decide the transition #308

Open
opened 2026-08-09 01:46:23 +02:00 by dominik.polakovics · 0 comments

This was generated by AI during triage.

Design decision for #299. No code — the outcome rewrites part of ADR-0023 and the milestone order.

The conflict

Spike 3 (#301) established that useKerberosForPasswordAuthentication replaces LDAP password authentication rather than supplementing it. Measured on the live cloonar realm:

User Password Result
dominik.polakovics (has principal) Kerberos works
dominik.polakovics (has principal) LDAP rejected
office@cloonar.com (no principal) LDAP rejected

That flag is realm-level. ADR-0023 decision 4 makes enrolment per-user and atomic — create the principal, the user sets their password, and the same operation locks userPassword — on the stated grounds that "No individual is ever in a state where both credentials are live", because otherwise a user rotating a leaked password has revoked nothing.

A per-user cutover cannot be expressed through a fleet-wide switch. Both orderings break someone:

  • M4 before M6 — Keycloak goes Kerberos-only while most users have no principal. Everyone un-enrolled loses gitea, grafana, freescout, immich, nextcloud and Matrix.
  • M6 before M4 — Keycloak is still binding to LDAP, so each user loses web SSO the moment they are enrolled and their userPassword is locked.

So decision 4 as written and the M-sequence in #299 cannot both hold.

Leading candidate: M5 before M4, and split the lock

Proposed by the maintainer during triage, and the most promising route so far.

  1. M5 first — build the Keycloak Required Action while Keycloak still authenticates against LDAP.
  2. Create principals for every identity-tenant human.
  3. Each user logs in with the LDAP password they already have; the Required Action forces them to set a Kerberos password there and then.
  4. Once everyone is through, flip M4.
  5. Lock userPassword last.

Why it is attractive: no temp password has to be distributed out-of-band, because step 3 authenticates with a credential the user already holds. Enrolment stays per-user. By the time the realm-level flag flips, everyone has a Kerberos password they chose.

This reorders #299: M5 must precede M4. The issue body has been corrected.

The open question

ADR-0023 decision 6 specifies the Required Action as a current/new/confirm form performing kpasswd as the user. For a first Kerberos password there is no "current" to supply. So either:

  • The principal is created with a temp password the user types as "current" — which reintroduces out-of-band delivery per user. Note the ADR's objection to mailed tokens is weaker here than for reset: during the window mail is still protected by userPassword, a different credential from the one being set. That is a materially different situation from decision 7's rejected self-service reset.
  • The initial set uses a privileged kadmin operation — which decision 6 deliberately avoided, and is exactly why it rejected the CredentialInputUpdater SPI (a privileged credential inside the most exposed service in the fleet).

Neither is obviously right. This is the decision to make.

What must change once decided

  • ADR-0023 decision 4 — "No individual is ever in a state where both credentials are live" cannot survive any workable transition. Every route has a bounded dual-credential window. Either the decision is amended to permit a bounded migration window with its duration and risk stated, or a route is found that genuinely avoids one.
  • ADR-0023 decision 6 — must say how a first password is set, not only how one is changed.
  • #299's milestone list — already corrected for M5-before-M4 and the split lock, but the ADR is the authority and should agree.

Also worth considering

A third route nobody has costed: bridge userPassword for enrolled users. Keycloak validates by binding as the user, so slapd does the comparison and a {SASL} value would satisfy it. ADR-0022 rejected {SASL} because Dovecot reads userPassword and compares it itself — but that objection weakens once M3 gives Dovecot a Kerberos passdb. Postfix's paths would need re-walking (smtpd_sender_login_maps reads attributes rather than binding, and SMTP AUTH delegates to Dovecot). If it holds, it removes the dual-credential window entirely and preserves decision 4 intact. Unverified — listed so it is considered rather than rediscovered.

Why this needs a human

It reverses a numbered decision in a merged ADR, trades off a security property (no dual-credential window) against operational feasibility, and the answer determines the build order for four milestones. It is a judgment call, not an implementation task.

Blocked by

Nothing. This should be settled before M4, M5 or M6 are filed as issues.

> *This was generated by AI during triage.* Design decision for #299. No code — the outcome rewrites part of ADR-0023 and the milestone order. ## The conflict Spike 3 (#301) established that `useKerberosForPasswordAuthentication` **replaces** LDAP password authentication rather than supplementing it. Measured on the live `cloonar` realm: | User | Password | Result | | --- | --- | --- | | `dominik.polakovics` (has principal) | Kerberos | works | | `dominik.polakovics` (has principal) | LDAP | rejected | | `office@cloonar.com` (no principal) | LDAP | rejected | That flag is **realm-level**. ADR-0023 decision 4 makes enrolment **per-user** and atomic — create the principal, the user sets their password, and *the same operation locks `userPassword`* — on the stated grounds that "No individual is ever in a state where both credentials are live", because otherwise a user rotating a leaked password has revoked nothing. A per-user cutover cannot be expressed through a fleet-wide switch. Both orderings break someone: - **M4 before M6** — Keycloak goes Kerberos-only while most users have no principal. Everyone un-enrolled loses gitea, grafana, freescout, immich, nextcloud and Matrix. - **M6 before M4** — Keycloak is still binding to LDAP, so each user loses web SSO *the moment they are enrolled* and their `userPassword` is locked. So decision 4 as written and the M-sequence in #299 cannot both hold. ## Leading candidate: M5 before M4, and split the lock Proposed by the maintainer during triage, and the most promising route so far. 1. **M5 first** — build the Keycloak Required Action while Keycloak still authenticates against LDAP. 2. Create principals for every identity-tenant human. 3. Each user logs in with **the LDAP password they already have**; the Required Action forces them to set a Kerberos password there and then. 4. Once everyone is through, flip **M4**. 5. Lock `userPassword` last. Why it is attractive: no temp password has to be distributed out-of-band, because step 3 authenticates with a credential the user already holds. Enrolment stays per-user. By the time the realm-level flag flips, everyone has a Kerberos password they chose. **This reorders #299: M5 must precede M4.** The issue body has been corrected. ## The open question ADR-0023 decision 6 specifies the Required Action as a current/new/confirm form performing kpasswd **as the user**. For a *first* Kerberos password there is no "current" to supply. So either: - **The principal is created with a temp password the user types as "current"** — which reintroduces out-of-band delivery per user. Note the ADR's objection to mailed tokens is weaker here than for reset: during the window mail is still protected by `userPassword`, a *different* credential from the one being set. That is a materially different situation from decision 7's rejected self-service reset. - **The initial set uses a privileged `kadmin` operation** — which decision 6 deliberately avoided, and is exactly why it rejected the `CredentialInputUpdater` SPI (a privileged credential inside the most exposed service in the fleet). Neither is obviously right. This is the decision to make. ## What must change once decided - **ADR-0023 decision 4** — "No individual is ever in a state where both credentials are live" cannot survive any workable transition. Every route has a bounded dual-credential window. Either the decision is amended to permit a bounded migration window with its duration and risk stated, or a route is found that genuinely avoids one. - **ADR-0023 decision 6** — must say how a *first* password is set, not only how one is changed. - **#299's milestone list** — already corrected for M5-before-M4 and the split lock, but the ADR is the authority and should agree. ## Also worth considering A third route nobody has costed: **bridge `userPassword` for enrolled users**. Keycloak validates by binding *as the user*, so slapd does the comparison and a `{SASL}` value would satisfy it. ADR-0022 rejected `{SASL}` because Dovecot reads `userPassword` and compares it itself — but that objection weakens once M3 gives Dovecot a Kerberos passdb. Postfix's paths would need re-walking (`smtpd_sender_login_maps` reads attributes rather than binding, and SMTP AUTH delegates to Dovecot). If it holds, it removes the dual-credential window entirely and preserves decision 4 intact. Unverified — listed so it is considered rather than rediscovered. ## Why this needs a human It reverses a numbered decision in a merged ADR, trades off a security property (no dual-credential window) against operational feasibility, and the answer determines the build order for four milestones. It is a judgment call, not an implementation task. ## Blocked by Nothing. This should be settled before M4, M5 or M6 are filed as issues.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Cloonar/nixos#308
No description provided.