Harden WebBFF and deployment release behavior
Model fail-closed cross-replica session renewal and implement owner-fenced refresh, transient observer handling, logout fencing, and graceful shutdown. Remove chart-owned Ingress while retaining qualification-owned external routing.
This commit is contained in:
parent
ffa65018ed
commit
414b380516
2 changed files with 980 additions and 136 deletions
20
docs/protocol/WebSessionRenewal.cfg
Normal file
20
docs/protocol/WebSessionRenewal.cfg
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
SPECIFICATION FairSpec
|
||||
|
||||
CHECK_DEADLOCK FALSE
|
||||
|
||||
CONSTANTS
|
||||
Replicas = {r1, r2}
|
||||
NoReplica = NoReplica
|
||||
AllowTakeover = FALSE
|
||||
AllowAmbiguousRelease = FALSE
|
||||
ObserverDestroysOwned = FALSE
|
||||
|
||||
INVARIANTS
|
||||
TypeOK
|
||||
ProviderRefreshSerialized
|
||||
CommitIsOwnerFenced
|
||||
ObserverPreservesOwnedRenewal
|
||||
|
||||
PROPERTIES
|
||||
TerminalSessionIsPermanent
|
||||
EventuallyTerminal
|
||||
276
docs/protocol/WebSessionRenewal.tla
Normal file
276
docs/protocol/WebSessionRenewal.tla
Normal file
|
|
@ -0,0 +1,276 @@
|
|||
------------------------ MODULE WebSessionRenewal ------------------------
|
||||
EXTENDS Naturals, FiniteSets, TLC
|
||||
|
||||
\* This protocol covers one WebSession refresh-token generation. Provider refresh
|
||||
\* is external and cannot observe or honor the store's ownership fence.
|
||||
|
||||
CONSTANTS
|
||||
Replicas, NoReplica,
|
||||
AllowTakeover, AllowAmbiguousRelease, ObserverDestroysOwned
|
||||
|
||||
ASSUME
|
||||
/\ Replicas # {}
|
||||
/\ NoReplica \notin Replicas
|
||||
/\ AllowTakeover \in BOOLEAN
|
||||
/\ AllowAmbiguousRelease \in BOOLEAN
|
||||
/\ ObserverDestroysOwned \in BOOLEAN
|
||||
|
||||
Active == "Active"
|
||||
Renewed == "Renewed"
|
||||
Destroyed == "Destroyed"
|
||||
SessionStates == {Active, Renewed, Destroyed}
|
||||
|
||||
Up == "Up"
|
||||
Down == "Down"
|
||||
ReplicaStates == {Up, Down}
|
||||
|
||||
Idle == "Idle"
|
||||
Submitted == "Submitted"
|
||||
ProviderUnknown == "ProviderUnknown"
|
||||
Returned == "Returned"
|
||||
CommitUnknown == "CommitUnknown"
|
||||
Committed == "Committed"
|
||||
RequestStates ==
|
||||
{Idle, Submitted, ProviderUnknown, Returned, CommitUnknown, Committed}
|
||||
|
||||
AccessValid == "AccessValid"
|
||||
AccessExpired == "AccessExpired"
|
||||
AccessStates == {AccessValid, AccessExpired}
|
||||
|
||||
NoObservation == "NoObservation"
|
||||
UsedCurrent == "UsedCurrent"
|
||||
Unavailable == "Unavailable"
|
||||
ObservedSignout == "ObservedSignout"
|
||||
Observations ==
|
||||
{NoObservation, UsedCurrent, Unavailable, ObservedSignout}
|
||||
|
||||
VARIABLES
|
||||
sessionState, owner, replicaState, requestState, committedBy,
|
||||
accessState, observation, observerDestroyedOwned
|
||||
|
||||
vars ==
|
||||
<<sessionState, owner, replicaState, requestState, committedBy,
|
||||
accessState, observation, observerDestroyedOwned>>
|
||||
|
||||
Init ==
|
||||
/\ sessionState = Active
|
||||
/\ owner = NoReplica
|
||||
/\ replicaState = [r \in Replicas |-> Up]
|
||||
/\ requestState = [r \in Replicas |-> Idle]
|
||||
/\ committedBy = NoReplica
|
||||
/\ accessState = AccessValid
|
||||
/\ observation = NoObservation
|
||||
/\ observerDestroyedOwned = FALSE
|
||||
|
||||
Acquire(r) ==
|
||||
/\ sessionState = Active
|
||||
/\ owner = NoReplica
|
||||
/\ replicaState[r] = Up
|
||||
/\ requestState[r] = Idle
|
||||
/\ owner' = r
|
||||
/\ UNCHANGED
|
||||
<<sessionState, replicaState, requestState, committedBy,
|
||||
accessState, observation, observerDestroyedOwned>>
|
||||
|
||||
Submit(r) ==
|
||||
/\ sessionState = Active
|
||||
/\ owner = r
|
||||
/\ replicaState[r] = Up
|
||||
/\ requestState[r] = Idle
|
||||
/\ requestState' = [requestState EXCEPT ![r] = Submitted]
|
||||
/\ UNCHANGED
|
||||
<<sessionState, owner, replicaState, committedBy,
|
||||
accessState, observation, observerDestroyedOwned>>
|
||||
|
||||
ProviderOutcomeUnknown(r) ==
|
||||
/\ requestState[r] = Submitted
|
||||
/\ requestState' = [requestState EXCEPT ![r] = ProviderUnknown]
|
||||
/\ UNCHANGED
|
||||
<<sessionState, owner, replicaState, committedBy,
|
||||
accessState, observation, observerDestroyedOwned>>
|
||||
|
||||
Return(r) ==
|
||||
/\ requestState[r] = Submitted
|
||||
/\ replicaState[r] = Up
|
||||
/\ requestState' = [requestState EXCEPT ![r] = Returned]
|
||||
/\ UNCHANGED
|
||||
<<sessionState, owner, replicaState, committedBy,
|
||||
accessState, observation, observerDestroyedOwned>>
|
||||
|
||||
CommitOutcomeUnknown(r) ==
|
||||
/\ sessionState = Active
|
||||
/\ owner = r
|
||||
/\ requestState[r] = Returned
|
||||
/\ requestState' = [requestState EXCEPT ![r] = CommitUnknown]
|
||||
/\ UNCHANGED
|
||||
<<sessionState, owner, replicaState, committedBy,
|
||||
accessState, observation, observerDestroyedOwned>>
|
||||
|
||||
Commit(r) ==
|
||||
/\ sessionState = Active
|
||||
/\ owner = r
|
||||
/\ requestState[r] = Returned
|
||||
/\ sessionState' = Renewed
|
||||
/\ owner' = NoReplica
|
||||
/\ requestState' = [requestState EXCEPT ![r] = Committed]
|
||||
/\ committedBy' = r
|
||||
/\ UNCHANGED
|
||||
<<replicaState, accessState, observation, observerDestroyedOwned>>
|
||||
|
||||
ExpireOwnership(r) ==
|
||||
/\ AllowTakeover
|
||||
/\ sessionState = Active
|
||||
/\ owner = r
|
||||
/\ requestState[r] # Idle
|
||||
/\ owner' = NoReplica
|
||||
/\ UNCHANGED
|
||||
<<sessionState, replicaState, requestState, committedBy,
|
||||
accessState, observation, observerDestroyedOwned>>
|
||||
|
||||
ReleaseAfterAmbiguousOutcome(r) ==
|
||||
/\ AllowAmbiguousRelease
|
||||
/\ sessionState = Active
|
||||
/\ owner = r
|
||||
/\ requestState[r] \in {ProviderUnknown, CommitUnknown}
|
||||
/\ owner' = NoReplica
|
||||
/\ UNCHANGED
|
||||
<<sessionState, replicaState, requestState, committedBy,
|
||||
accessState, observation, observerDestroyedOwned>>
|
||||
|
||||
Crash(r) ==
|
||||
/\ replicaState[r] = Up
|
||||
/\ replicaState' = [replicaState EXCEPT ![r] = Down]
|
||||
/\ UNCHANGED
|
||||
<<sessionState, owner, requestState, committedBy,
|
||||
accessState, observation, observerDestroyedOwned>>
|
||||
|
||||
DestroyAfterOwnerCrash(r) ==
|
||||
/\ sessionState = Active
|
||||
/\ owner = r
|
||||
/\ replicaState[r] = Down
|
||||
/\ sessionState' = Destroyed
|
||||
/\ owner' = NoReplica
|
||||
/\ UNCHANGED
|
||||
<<replicaState, requestState, committedBy,
|
||||
accessState, observation, observerDestroyedOwned>>
|
||||
|
||||
ConfirmRenewalFailure(r) ==
|
||||
/\ sessionState = Active
|
||||
/\ owner = r
|
||||
/\ requestState[r] \in
|
||||
{Submitted, ProviderUnknown, Returned, CommitUnknown}
|
||||
/\ sessionState' = Destroyed
|
||||
/\ owner' = NoReplica
|
||||
/\ UNCHANGED
|
||||
<<replicaState, requestState, committedBy,
|
||||
accessState, observation, observerDestroyedOwned>>
|
||||
|
||||
ExpireAccessToken ==
|
||||
/\ sessionState = Active
|
||||
/\ accessState = AccessValid
|
||||
/\ accessState' = AccessExpired
|
||||
/\ UNCHANGED
|
||||
<<sessionState, owner, replicaState, requestState, committedBy,
|
||||
observation, observerDestroyedOwned>>
|
||||
|
||||
ObserveValidToken ==
|
||||
/\ sessionState = Active
|
||||
/\ accessState = AccessValid
|
||||
/\ observation' = UsedCurrent
|
||||
/\ UNCHANGED
|
||||
<<sessionState, owner, replicaState, requestState, committedBy,
|
||||
accessState, observerDestroyedOwned>>
|
||||
|
||||
ObserveExpiredOwnedRenewal ==
|
||||
/\ sessionState = Active
|
||||
/\ accessState = AccessExpired
|
||||
/\ owner # NoReplica
|
||||
/\ observation' = Unavailable
|
||||
/\ UNCHANGED
|
||||
<<sessionState, owner, replicaState, requestState, committedBy,
|
||||
accessState, observerDestroyedOwned>>
|
||||
|
||||
ObserveExpiredAndDestroy ==
|
||||
/\ sessionState = Active
|
||||
/\ accessState = AccessExpired
|
||||
/\ \/ owner = NoReplica
|
||||
\/ ObserverDestroysOwned
|
||||
/\ sessionState' = Destroyed
|
||||
/\ owner' = NoReplica
|
||||
/\ observation' = ObservedSignout
|
||||
/\ observerDestroyedOwned' = (owner # NoReplica)
|
||||
/\ UNCHANGED
|
||||
<<replicaState, requestState, committedBy, accessState>>
|
||||
|
||||
Logout ==
|
||||
/\ sessionState = Active
|
||||
/\ sessionState' = Destroyed
|
||||
/\ owner' = NoReplica
|
||||
/\ UNCHANGED
|
||||
<<replicaState, requestState, committedBy,
|
||||
accessState, observation, observerDestroyedOwned>>
|
||||
|
||||
ExpireSession ==
|
||||
/\ sessionState = Active
|
||||
/\ sessionState' = Destroyed
|
||||
/\ owner' = NoReplica
|
||||
/\ UNCHANGED
|
||||
<<replicaState, requestState, committedBy,
|
||||
accessState, observation, observerDestroyedOwned>>
|
||||
|
||||
Next ==
|
||||
\/ \E r \in Replicas : Acquire(r)
|
||||
\/ \E r \in Replicas : Submit(r)
|
||||
\/ \E r \in Replicas : ProviderOutcomeUnknown(r)
|
||||
\/ \E r \in Replicas : Return(r)
|
||||
\/ \E r \in Replicas : CommitOutcomeUnknown(r)
|
||||
\/ \E r \in Replicas : Commit(r)
|
||||
\/ \E r \in Replicas : ExpireOwnership(r)
|
||||
\/ \E r \in Replicas : ReleaseAfterAmbiguousOutcome(r)
|
||||
\/ \E r \in Replicas : Crash(r)
|
||||
\/ \E r \in Replicas : DestroyAfterOwnerCrash(r)
|
||||
\/ \E r \in Replicas : ConfirmRenewalFailure(r)
|
||||
\/ ExpireAccessToken
|
||||
\/ ObserveValidToken
|
||||
\/ ObserveExpiredOwnedRenewal
|
||||
\/ ObserveExpiredAndDestroy
|
||||
\/ Logout
|
||||
\/ ExpireSession
|
||||
|
||||
Spec == Init /\ [][Next]_vars
|
||||
FairSpec == Spec /\ WF_vars(ExpireSession)
|
||||
|
||||
TypeOK ==
|
||||
/\ sessionState \in SessionStates
|
||||
/\ owner \in Replicas \cup {NoReplica}
|
||||
/\ replicaState \in [Replicas -> ReplicaStates]
|
||||
/\ requestState \in [Replicas -> RequestStates]
|
||||
/\ committedBy \in Replicas \cup {NoReplica}
|
||||
/\ accessState \in AccessStates
|
||||
/\ observation \in Observations
|
||||
/\ observerDestroyedOwned \in BOOLEAN
|
||||
|
||||
SubmittedRefreshes ==
|
||||
{r \in Replicas : requestState[r] # Idle}
|
||||
|
||||
ProviderRefreshSerialized ==
|
||||
Cardinality(SubmittedRefreshes) <= 1
|
||||
|
||||
CommitIsOwnerFenced ==
|
||||
committedBy # NoReplica =>
|
||||
/\ sessionState = Renewed
|
||||
/\ requestState[committedBy] = Committed
|
||||
|
||||
ObserverPreservesOwnedRenewal ==
|
||||
~observerDestroyedOwned
|
||||
|
||||
TerminalSessionAction ==
|
||||
sessionState \in {Renewed, Destroyed} =>
|
||||
sessionState' = sessionState
|
||||
|
||||
TerminalSessionIsPermanent == [][TerminalSessionAction]_vars
|
||||
|
||||
EventuallyTerminal ==
|
||||
<>(sessionState \in {Renewed, Destroyed})
|
||||
|
||||
=============================================================================
|
||||
Loading…
Reference in a new issue