raghav-reglobe opened a new pull request, #66115: URL: https://github.com/apache/doris/pull/66115
### What problem does this PR solve? Issue: #66113 `ALTER USER ... IDENTIFIED BY` is a hard cutover — the old password stops working the instant the statement commits, so every client embedding that account's password must be updated simultaneously or fail to authenticate. This makes online credential rotation impossible without a maintenance window. This adds MySQL 8.0-compatible **dual password** support so a password can be rotated while the previous one keeps working, letting consumers migrate on their own schedule: ```sql ALTER USER u IDENTIFIED BY 'new' RETAIN CURRENT PASSWORD; -- old password stays valid ALTER USER u IDENTIFIED BY 'new'; -- secondary unchanged ALTER USER u DISCARD OLD PASSWORD; -- evict the retained one ``` Doris already mirrors MySQL account management (`PASSWORD_HISTORY`, `PASSWORD_EXPIRE`, `FAILED_LOGIN_ATTEMPTS`, `PASSWORD_LOCK_TIME`); this fills the remaining gap — `PASSWORD_EXPIRE` can force a rotation, but there was no way to comply with it online. ### Semantics (mirrors the MySQL 8.0 manual) | Rule | MySQL manual | |---|---| | `RETAIN CURRENT PASSWORD` saves the current primary as the secondary; both authenticate | "the current password ... is retained as a secondary password" | | Exactly one secondary per user; the next `RETAIN` replaces it | dual = primary + one secondary | | A password change **without** `RETAIN` leaves an existing secondary **unchanged** | "the secondary password remains unchanged" | | An **empty** new password empties the secondary too, even with `RETAIN` | "the secondary password becomes empty as well" | | `RETAIN` on an account with an **empty primary** fails | "the statement fails" | | `DISCARD OLD PASSWORD` drops the secondary if one exists (no-op otherwise); cannot combine with a password change | "discards the secondary password, if one exists" | ### Design - **Storage**: `Password` gains a nullable secondary slot (GSON field `pwd2`). Absent in images/journals written before this change; ignored by binaries without it — tolerant in both directions, so no `FeMetaVersion` bump and rolling upgrade / downgrade is safe. - **Authentication**: primary is tried first, then the secondary, in one shared helper behind **both** existing check paths (`checkPasswordInternal` and `checkPasswordInternalForUser`), so every protocol lane (MySQL scramble, cleartext/LDAP fallback, Arrow Flight, HTTP, stream load) behaves identically. Failed-login policy fires only when both miss. - **Replay**: the retain flag rides `PrivInfo` on the existing `OP_SET_PASSWORD`; `DISCARD` is a new `AlterUserOpType` on `OP_ALTER_USER`. No new operation types. - **Observability**: authenticating with the retained secondary logs an INFO line and increments a new FE metric `doris_fe_secondary_password_auth_total`, so operators can confirm every consumer has converged on the new password before the next rotation evicts the old one. - **Grammar**: `RETAIN`/`CURRENT`/`PASSWORD` tokens already existed; `DISCARD` and `OLD` are added as **nonReserved** keywords (still usable as identifiers). ### Deliberate differences from MySQL (called out for review) - **Privileges**: Doris has no dynamic-privilege framework, so the clauses require the same privilege as any other `ALTER USER` password change (MySQL gates them behind `APPLICATION_PASSWORD_ADMIN` / `CREATE USER`). Happy to add a privilege check if the maintainers prefer. - **No per-user authentication plugins** in Doris, so MySQL's "changing the authentication plugin discards the secondary" rule has no analog. ### Out of scope (follow-ups) - `SET PASSWORD ... RETAIN CURRENT PASSWORD` statement form (this PR covers `ALTER USER`). - Introspection of whether a user currently holds a retained password. ### Rolling upgrade All FEs should run this version before the new clauses are first used (masters last, the usual order). Mixed-version behavior is safe: a binary without the change ignores the `pwd2` field and the retain flag (single-password behavior). ### Tests `DualPasswordTest` (7 cases): retain / evict / discard chain; secondary preserved on a plain change; empty-new empties the secondary; retain-on-empty-primary fails; replay (retain via `PrivInfo`, discard via `AlterUserOperationLog`, and legacy-journal compatibility); GSON forward/backward compatibility; parser (clauses parse, retain-without-password rejected, `DISCARD`/`OLD` still valid as identifiers). ### Checklist - [x] Regression test: `fe/fe-core/.../mysql/privilege/DualPasswordTest.java` - [x] Compatible with rolling upgrade (GSON-tolerant field, no meta-version bump) ### Behavior change `ALTER USER` gains optional `RETAIN CURRENT PASSWORD` / `DISCARD OLD PASSWORD` clauses. No behavior change for statements that don't use them. ### Release note Support MySQL-compatible dual password on `ALTER USER` (`RETAIN CURRENT PASSWORD` / `DISCARD OLD PASSWORD`) for zero-downtime credential rotation. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected] --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
