fix(auth): sweep refresh revocations until a pass finds nothing (#725) - #890
Merged
Merged
Conversation
#775 closed the race on SQLite, but on Postgres a revoking DELETE only sees rows committed before it started. Record the gap, the sweep that closes it and the owner's scope decisions. Signed-off-by: alex-dembele <alexandredembele16@gmail.com>
Under READ COMMITTED a revoking DELETE misses a successor that a rotation commits while the DELETE runs, and the rotation still sees its witness because the DELETE has not committed. Family revocation, logout-everywhere and per-tenant revocation all left a live token this way. Repeating the DELETE until it removes nothing closes the gap; the ordering argument sits next to the sweep and the witness check. A Postgres test parks the DELETE behind a row lock to force the interleaving for all three revokers, and a SQLite test checks that the sweep takes a late row and stops. Signed-off-by: alex-dembele <alexandredembele16@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #725
What was wrong
#775 fixed the race #725 describes: after storing its successor, a rotation checks that its claimed row (the witness) still exists. That check only works once the revoking
DELETEhas committed. On Postgres, under READ COMMITTED, aDELETEreads from the snapshot taken when it starts, and its deletions stay invisible until it commits. So a rotation can store its successor after that snapshot, still see the witness, and hand the successor out, which survives the revocation.This affects all three revokers:
revokeFamily, on reuse or lost membership;RevokeAllUserTokens, on password change or reset;RevokeUserTokensInTenant, on member deactivation or role change.SQLite serialises writers, so the existing tests could not show it.
Mechanism
sweepRefreshTokensrepeats theDELETEuntil a pass removes nothing, with at most 5 passes. Let P be the last, empty pass. A successor committed before P started was visible to an earlier pass, so it is gone. A successor committed after P started has its witness checked after the earlier passes committed, so the rotation finds no witness, revokes the family itself and refuses. The full argument is intoken.go, next to the sweep and next to the witness check.The refresh path is unchanged: no lock, no transaction, no extra query. Revocation costs one extra
DELETEthat removes nothing. There is no schema change and no new dependency.Tests
token_pg_test.go(new, needsDATABASE_URL, runs on its own schema): parks the revokingDELETEbehind a row lock to force the interleaving, for all three revokers. It fails on master (1 token survives in each case) and passes with the fix.TestSweepRefreshTokens_TakesARowStoredBehindIt(SQLite): fails on master, passes with the fix.go test ./...: 78 packages pass.internal/handlerandcmd/serverfail to build on master itself (saml2_handler.go, #886), and this branch does not touch them.Spec:
docs/725_REFRESH_REVOCATION_RACE.md. This touches auth, so it should get a tech-lead review before merge.