95 lines
3.9 KiB
Go
95 lines
3.9 KiB
Go
package main
|
|
|
|
// The record of a bus account's rotation, and its one rule (novox/hq ADR 0561 §4; issue 774, the lead's decision of
|
|
// 2026-10-27). A module's, a process's or a provisioner client's account rotates over two credentials (ADR 0114 §10):
|
|
// a second login, `<user>--b`, carrying the first login's permissions verbatim, which the bus composer composes only while
|
|
// the account's key in `bus-accounts` names it in `rotating`. The vault starts at most one rotation per revision of the
|
|
// user list the bus loaded. What composes and delivers the second login is a subtask of its own, before the lease write;
|
|
// until it is built nothing begins a rotation, and `rotate` of a covered account stays refused, so nothing rotates in
|
|
// place.
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// SecondLoginSuffix makes an account's second login: reserved, so no module or process may be named to end in it.
|
|
const SecondLoginSuffix = "--b"
|
|
|
|
// Rotating is a rotation under way, as the account's key in bus-accounts says it: the second login, the generation of
|
|
// the value it carries, since when, and the revision of the loaded user list it began at.
|
|
type Rotating struct {
|
|
Login string `json:"login"`
|
|
Generation int `json:"generation"`
|
|
Since time.Time `json:"since"`
|
|
Revision uint64 `json:"revision"`
|
|
}
|
|
|
|
// LoadedListHeard keeps the revision of the user list the bus node says it loaded (its have of the bus module).
|
|
func (v *Vault) LoadedListHeard(revision uint64) {
|
|
v.minting.mu.Lock()
|
|
defer v.minting.mu.Unlock()
|
|
if revision > v.minting.loadedRevision {
|
|
v.minting.loadedRevision = revision
|
|
}
|
|
}
|
|
|
|
// BeginRotation records a rotation of user's account at the loaded list's revision, under the one rule: none for a
|
|
// node's account or the controller's, none while minting is off, none of an account the vault did not make or was not
|
|
// handed, none twice for one account, and none when another began at this revision.
|
|
func (v *Vault) BeginRotation(user string) (Rotating, error) {
|
|
if nodeOrController(user) {
|
|
return Rotating{}, fmt.Errorf("%s does not rotate here: a node's account and the controller's keep the hash they have (ADR 0561 §4)", user)
|
|
}
|
|
if on, why := v.leaseHanded(); !on {
|
|
return Rotating{}, fmt.Errorf("no rotation begins: %s", why)
|
|
}
|
|
v.minting.mu.Lock()
|
|
revision := v.minting.loadedRevision
|
|
v.minting.mu.Unlock()
|
|
if revision == 0 {
|
|
return Rotating{}, fmt.Errorf("no rotation begins: the revision of the user list the bus loaded is not known yet")
|
|
}
|
|
users, err := v.Accounts.Users()
|
|
if err != nil {
|
|
return Rotating{}, err
|
|
}
|
|
var mine *AccountHash
|
|
began := ""
|
|
for _, u := range users {
|
|
a, err := v.Accounts.Get(u)
|
|
if err != nil {
|
|
return Rotating{}, err
|
|
}
|
|
if a == nil {
|
|
continue
|
|
}
|
|
if a.Rotating != nil && a.Rotating.Revision == revision {
|
|
began = u
|
|
}
|
|
if u == user {
|
|
mine = a
|
|
}
|
|
}
|
|
switch {
|
|
case mine == nil || mine.Adopted || mine.Of == "":
|
|
return Rotating{}, fmt.Errorf("the vault holds no account of %s it made or was handed, so it rotates none", user)
|
|
case mine.Rotating != nil:
|
|
return Rotating{}, fmt.Errorf("%s is rotating already, since %s (second login %s)", user, mine.Rotating.Since.Format(time.RFC3339), mine.Rotating.Login)
|
|
case strings.HasSuffix(user, SecondLoginSuffix):
|
|
return Rotating{}, fmt.Errorf("%s is a second login, which never rotates of its own", user)
|
|
case began != "":
|
|
return Rotating{}, fmt.Errorf("no rotation of %s begins: %s's began at the loaded list's revision %d, and one "+
|
|
"rotation begins per revision (ADR 0561 §4)", user, began, revision)
|
|
}
|
|
r := Rotating{Login: user + SecondLoginSuffix, Generation: mine.Generation + 1, Since: v.Now().UTC(), Revision: revision}
|
|
next := *mine
|
|
next.Rotating = &r
|
|
if err := v.Accounts.Put(next); err != nil {
|
|
return Rotating{}, err
|
|
}
|
|
v.log("a rotation of %s begins at the loaded list's revision %d: second login %s", user, revision, r.Login)
|
|
return r, nil
|
|
}
|