mesh-vault: mint each account the grants part wants and the vault holds no value or adopted hash for, in the controller's shape, hashed before account-issued and secret-changed (issue 774, ADR 0561 §1, §2, §4)
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
package main
|
||||
|
||||
import "git.novox.be/novox/mesh-sdk/go/buscompose"
|
||||
|
||||
// LeaseHeard is the watch of the controller's lease (ADR 0561 §3): minting is on only while it is handed to a holder that
|
||||
// is no controller.
|
||||
func (v *Vault) LeaseHeard(raw []byte, found bool, readErr error) {
|
||||
on, why := buscompose.LeaseHandedAway(raw, found, readErr)
|
||||
m := &v.minting
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if on != m.on || m.why == "" {
|
||||
m.since = v.Now().UTC()
|
||||
}
|
||||
m.on, m.why = on, why
|
||||
}
|
||||
@@ -120,6 +120,23 @@ func watchHandings(env *Env) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// watchWants mints from the grants part's wanted accounts as they change (mint.go, ADR 0561 §2): a watch, never a poll.
|
||||
// It answers whether the watch began; one that did not is begun again at the next minute pass.
|
||||
func watchWants(env *Env) bool {
|
||||
if env.Vault == nil {
|
||||
return true
|
||||
}
|
||||
err := stdio.State(WantedRead).Watch("", func(c stdio.StateChange) error {
|
||||
env.Vault.WantHeard(c.Key, c.Value, c.Op == "delete" || c.Op == "purge" || len(c.Value) == 0)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
say("the grants part's wanted accounts are not watched, so nothing is minted; begun again at the next pass: %v", err)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// adoptOperatorKey takes the operator key from the export the mesh writes to the vault's root (what
|
||||
// `operator key set` recorded; the controller keeps only a public half there), and seals again any
|
||||
// operator copy not yet sealed to the current key. Run every minute, so a failure partway is finished. It
|
||||
@@ -174,6 +191,7 @@ func main() {
|
||||
say("hashed %d bus accounts at start; %d could not be, tried again every minute: %v", len(written), len(failed), failed)
|
||||
}
|
||||
watched, watching := watchHandings(env), watchAssignments()
|
||||
wants := watchWants(env)
|
||||
for {
|
||||
if !watched {
|
||||
watched = watchHandings(env)
|
||||
@@ -181,6 +199,9 @@ func main() {
|
||||
if !watching {
|
||||
watching = watchAssignments()
|
||||
}
|
||||
if !wants {
|
||||
wants = watchWants(env)
|
||||
}
|
||||
env.pass()
|
||||
if env.Vault != nil {
|
||||
env.Vault.HashOwed()
|
||||
|
||||
@@ -0,0 +1,287 @@
|
||||
package main
|
||||
|
||||
// The vault mints every new bus account (novox/hq ADR 0561 §1 to §4, issue 774).
|
||||
//
|
||||
// The grants part says which accounts the mesh wants, in its state `accounts` that the vault alone reads (§2): one key
|
||||
// per bus user, holding what the controller's credential carries beside the password. The vault watches it, and for a
|
||||
// user it holds no value for and whose hash it did not adopt it makes a password, keeps the account whole (built by
|
||||
// mesh-sdk's HeldCredential, byte for byte the controller's shape) as the module's own secret on that node
|
||||
// (`own/<node>/<module>/<broker|broker-<process>|provisioner-broker>`), sealed, with the operator's copy; writes the
|
||||
// bcrypt hash into `bus-accounts`; and says `account-issued`, then `secret-changed`. The node's engine asks `own` again
|
||||
// on `secret-changed` and composes.
|
||||
//
|
||||
// **One issuer at every moment** (§3): minting is on only while the controller's lease is handed to a holder that is no
|
||||
// controller (mesh-sdk LeaseHandedAway). Until then the controller mints, the vault keeps hashing what it is handed, and
|
||||
// vault_status names every wanted account it holds back and why. **Nothing is minted anew** (§4): an account whose value
|
||||
// the vault holds, or whose hash it adopted, is never minted; the node accounts and the controller's own are never wanted.
|
||||
// **Never two values for one user** (§7): a user whose hash the vault already wrote from another of its secrets is held
|
||||
// back, said, and never replaced but by a rotation.
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/buscompose"
|
||||
)
|
||||
|
||||
// WantedRead is the grants part's state of wanted accounts, as the vault's manifest reads it.
|
||||
const WantedRead = buscompose.GrantsAccounts
|
||||
|
||||
// LeaseRead is the controller's lease, as the vault's manifest reads it, and LeaseKey its one key.
|
||||
const (
|
||||
LeaseRead = buscompose.ControllerLease
|
||||
LeaseKey = buscompose.ControllerLeaseKey
|
||||
)
|
||||
|
||||
// AccountIssuedEvent is what the vault says when it minted an account (mesh-sdk buscompose.AccountIssued).
|
||||
const AccountIssuedEvent = buscompose.AccountIssuedEvent
|
||||
|
||||
// minting is the switch and what the vault knows of the wanted accounts, kept in memory and read again from the bus at
|
||||
// every start: the grants part's state and the lease are the record, never this.
|
||||
type minting struct {
|
||||
mu sync.Mutex
|
||||
on bool
|
||||
why string
|
||||
since time.Time
|
||||
// wants are the wanted accounts, by user, as last heard.
|
||||
wants map[string]buscompose.WantedAccount
|
||||
// heldBack are the wanted users the vault did not mint, with why (vault_status).
|
||||
heldBack map[string]string
|
||||
// minted are the accounts this vault minted since it started, by user (vault_status).
|
||||
minted map[string]Minted
|
||||
// mintMu keeps one mint at a time: a want heard, the switch turning on, and issue-account.
|
||||
mintMu sync.Mutex
|
||||
}
|
||||
|
||||
// Minted is what one mint did: the account and its secret, never its value or its hash.
|
||||
type Minted struct {
|
||||
User string `json:"user"`
|
||||
Secret string `json:"secret"`
|
||||
Generation int `json:"generation"`
|
||||
At time.Time `json:"at"`
|
||||
// Hashed says its hash is written; false leaves it owed, written at the next pass.
|
||||
Hashed bool `json:"hashed"`
|
||||
}
|
||||
|
||||
// MintState is what vault_status says of minting.
|
||||
type MintState struct {
|
||||
On bool `json:"on"`
|
||||
Why string `json:"why"`
|
||||
Since time.Time `json:"since,omitzero"`
|
||||
Wanted int `json:"wanted"`
|
||||
HeldBack map[string]string `json:"held_back"`
|
||||
Minted []Minted `json:"minted"`
|
||||
}
|
||||
|
||||
// MintState answers how minting stands.
|
||||
func (v *Vault) MintState() MintState {
|
||||
m := &v.minting
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
out := MintState{On: m.on, Why: m.why, Since: m.since, Wanted: len(m.wants), HeldBack: map[string]string{}, Minted: []Minted{}}
|
||||
if out.Why == "" {
|
||||
out.Why = "the controller's lease has not been read yet, so nothing is minted"
|
||||
}
|
||||
for u, why := range m.heldBack {
|
||||
out.HeldBack[u] = why
|
||||
}
|
||||
for _, x := range m.minted {
|
||||
out.Minted = append(out.Minted, x)
|
||||
}
|
||||
sort.Slice(out.Minted, func(i, j int) bool { return out.Minted[i].User < out.Minted[j].User })
|
||||
return out
|
||||
}
|
||||
|
||||
// mintingOn says whether the vault mints now, and why.
|
||||
func (v *Vault) mintingOn() (bool, string) {
|
||||
v.minting.mu.Lock()
|
||||
defer v.minting.mu.Unlock()
|
||||
if v.minting.why == "" {
|
||||
return false, "the controller's lease has not been read yet, so nothing is minted"
|
||||
}
|
||||
return v.minting.on, v.minting.why
|
||||
}
|
||||
|
||||
func (v *Vault) holdBack(user, why string) {
|
||||
m := &v.minting
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.heldBack == nil {
|
||||
m.heldBack = map[string]string{}
|
||||
}
|
||||
m.heldBack[user] = why
|
||||
}
|
||||
|
||||
func (v *Vault) noLongerHeld(user string) {
|
||||
m := &v.minting
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
delete(m.heldBack, user)
|
||||
}
|
||||
|
||||
// AccountSecret is the vault's name of the own secret a wanted account is held in.
|
||||
func AccountSecret(w buscompose.WantedAccount) (string, error) {
|
||||
return OwnSecret(w.Node, w.Module, w.OwnSecret())
|
||||
}
|
||||
|
||||
// WantHeard is the watch of the grants part's wanted accounts: a key put is a want, minted when minting is on; a key
|
||||
// deleted is a want gone (retire.go).
|
||||
func (v *Vault) WantHeard(user string, raw []byte, deleted bool) {
|
||||
if deleted {
|
||||
v.wantGone(user)
|
||||
return
|
||||
}
|
||||
w, err := buscompose.ReadWantedAccount(user, raw)
|
||||
if err != nil {
|
||||
v.log("the wanted account %s is not read, so nothing is minted for it: %v", user, err)
|
||||
v.holdBack(user, "its wanted key is not readable: "+err.Error())
|
||||
return
|
||||
}
|
||||
v.minting.mu.Lock()
|
||||
if v.minting.wants == nil {
|
||||
v.minting.wants = map[string]buscompose.WantedAccount{}
|
||||
}
|
||||
v.minting.wants[user] = w
|
||||
v.minting.mu.Unlock()
|
||||
v.wantBack(user)
|
||||
if _, err := v.mintWanted(w, "the grants part's want of "+user); err != nil {
|
||||
v.log("%s is not minted: %v", user, err)
|
||||
}
|
||||
}
|
||||
|
||||
// errHeldBack is a want the vault does not mint now, with why: not an error of the vault.
|
||||
type errHeldBack struct{ why string }
|
||||
|
||||
func (e errHeldBack) Error() string { return e.why }
|
||||
|
||||
// mintWanted mints w when minting is on, the vault holds no value for it and adopted no hash of it; otherwise it says
|
||||
// why not and, where that is a hold, keeps it for vault_status.
|
||||
func (v *Vault) mintWanted(w buscompose.WantedAccount, by string) (Minted, error) {
|
||||
v.minting.mintMu.Lock()
|
||||
defer v.minting.mintMu.Unlock()
|
||||
if err := w.Valid(); err != nil {
|
||||
v.holdBack(w.User, err.Error())
|
||||
return Minted{}, err
|
||||
}
|
||||
secret, err := AccountSecret(w)
|
||||
if err != nil {
|
||||
v.holdBack(w.User, err.Error())
|
||||
return Minted{}, err
|
||||
}
|
||||
if _, held, err := v.Store.Read(secret); err != nil {
|
||||
return Minted{}, err
|
||||
} else if held {
|
||||
// Its value is held (the controller's, handed in, or one the vault minted): never minted anew (§4).
|
||||
v.noLongerHeld(w.User)
|
||||
return Minted{}, errHeldBack{secret + " is held already: an account the vault holds a value for is never minted anew (ADR 0561 §4)"}
|
||||
}
|
||||
if v.Accounts == nil {
|
||||
v.holdBack(w.User, "the vault has no state of bus accounts to write the hash to")
|
||||
return Minted{}, errors.New("the vault has no state of bus accounts to write the hash to")
|
||||
}
|
||||
was, err := v.Accounts.Get(w.User)
|
||||
if err != nil {
|
||||
v.holdBack(w.User, "its key in "+BusAccountsState+" cannot be read: "+err.Error())
|
||||
return Minted{}, err
|
||||
}
|
||||
if was != nil && was.Adopted {
|
||||
// The hash the bus loaded is kept (§4): not minted, and not held back either.
|
||||
v.noLongerHeld(w.User)
|
||||
return Minted{}, errHeldBack{w.User + "'s hash was adopted from the list the bus loaded: it keeps it, and is never minted (ADR 0561 §4)"}
|
||||
}
|
||||
if was != nil && was.Of != "" && was.Of != secret {
|
||||
why := fmt.Sprintf("%s holds the hash of %s for %s, not of %s: a user has one value, so nothing is minted until "+
|
||||
"a person sees why (ADR 0561 §7)", BusAccountsState, was.Of, w.User, secret)
|
||||
v.holdBack(w.User, why)
|
||||
return Minted{}, errHeldBack{why}
|
||||
}
|
||||
if on, why := v.mintingOn(); !on {
|
||||
v.holdBack(w.User, "held back: "+why)
|
||||
return Minted{}, errHeldBack{why}
|
||||
}
|
||||
raw := make([]byte, 30)
|
||||
if _, err := rand.Read(raw); err != nil {
|
||||
return Minted{}, err
|
||||
}
|
||||
value, err := buscompose.HeldCredential(w, base64.RawURLEncoding.EncodeToString(raw))
|
||||
if err != nil {
|
||||
v.holdBack(w.User, err.Error())
|
||||
return Minted{}, err
|
||||
}
|
||||
c, err := v.keepLocked(secret, string(value), "the vault", "", OriginMade, keepOptions{neverReplace: true, quiet: true})
|
||||
if err != nil {
|
||||
v.holdBack(w.User, "its value could not be kept: "+err.Error())
|
||||
return Minted{}, err
|
||||
}
|
||||
out := Minted{User: w.User, Secret: secret, Generation: c.Generation, At: c.At}
|
||||
// The hash first, then account-issued, then secret-changed: the grants part composes the user before the node asks.
|
||||
if _, err := v.HashAccount(secret, false); err != nil {
|
||||
v.hashes.owe(secret, err.Error())
|
||||
v.log("%s is minted and kept, and ITS HASH IS NOT WRITTEN, written at the next pass: %v", w.User, err)
|
||||
} else {
|
||||
out.Hashed = true
|
||||
if v.Emit != nil {
|
||||
e := buscompose.AccountIssued{User: w.User, Kind: w.Kind, Node: w.Node, Module: w.Module, Process: w.Process,
|
||||
Generation: c.Generation, At: c.At}
|
||||
body := map[string]any{}
|
||||
b, _ := json.Marshal(e)
|
||||
_ = json.Unmarshal(b, &body)
|
||||
if err := v.Emit(AccountIssuedEvent, body); err != nil {
|
||||
v.log("%s is minted and hashed, but %s could not be said: %v", w.User, AccountIssuedEvent, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
v.announce(c)
|
||||
v.minting.mu.Lock()
|
||||
if v.minting.minted == nil {
|
||||
v.minting.minted = map[string]Minted{}
|
||||
}
|
||||
v.minting.minted[w.User] = out
|
||||
v.minting.mu.Unlock()
|
||||
v.noLongerHeld(w.User)
|
||||
v.log("minted the bus account %s as %s (generation %d), for %s", w.User, secret, c.Generation, by)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// heldBackOrMinted is a mint's answer as a tool gives it.
|
||||
func heldBackOrMinted(m Minted, err error) (any, error) {
|
||||
var held errHeldBack
|
||||
if errors.As(err, &held) {
|
||||
return map[string]any{"minted": false, "why": held.why}, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return map[string]any{"minted": true, "account": m}, nil
|
||||
}
|
||||
|
||||
// wantedUsers is every wanted user heard, sorted.
|
||||
func (v *Vault) wantedUsers() []string {
|
||||
v.minting.mu.Lock()
|
||||
defer v.minting.mu.Unlock()
|
||||
out := make([]string, 0, len(v.minting.wants))
|
||||
for u := range v.minting.wants {
|
||||
out = append(out, u)
|
||||
}
|
||||
sort.Strings(out)
|
||||
return out
|
||||
}
|
||||
|
||||
func (v *Vault) wanted(user string) (buscompose.WantedAccount, bool) {
|
||||
v.minting.mu.Lock()
|
||||
defer v.minting.mu.Unlock()
|
||||
w, ok := v.minting.wants[user]
|
||||
return w, ok
|
||||
}
|
||||
|
||||
// nodeOrController says user is a node's account or the controller's: never minted here (ADR 0561 §4, §9).
|
||||
func nodeOrController(user string) bool {
|
||||
return user == ControllerCaller || strings.HasPrefix(user, "node.")
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/buscompose"
|
||||
"git.novox.be/novox/mesh-sdk/go/seal"
|
||||
)
|
||||
|
||||
// aWant is a wanted account of a module on ace, as the grants part writes it.
|
||||
func aWant(module string) buscompose.WantedAccount {
|
||||
return buscompose.WantedAccount{User: "ace." + module, Kind: buscompose.KindModule, Node: "ace", Module: module,
|
||||
URL: "nats://novox.mesh:4222", Fingerprint: "ab:cd", Claims: []buscompose.ClaimedSeat{{Seat: "s", Scope: "node"}}}
|
||||
}
|
||||
|
||||
func wantRaw(t *testing.T, w buscompose.WantedAccount) []byte {
|
||||
t.Helper()
|
||||
raw, err := json.Marshal(w)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return raw
|
||||
}
|
||||
|
||||
// mintingVault is a vault with a state of bus accounts, ace's sealing key stated, ace's modules assigned, and minting
|
||||
// turned on or off as on says.
|
||||
func mintingVault(t *testing.T, on bool) (testVault, *fakeAccounts, string) {
|
||||
t.Helper()
|
||||
plans := assignments{"ace.mod-x": `{"node":"ace","module":"mod-x"}`, "ace.mod-y": `{"node":"ace","module":"mod-y"}`}
|
||||
v := newVault(t, nil)
|
||||
v.Askers = Declared{Read: plans.read, Store: v.Store}
|
||||
accounts := &fakeAccounts{}
|
||||
v.Accounts = accounts
|
||||
pub, priv := aSealingKey(t)
|
||||
if err := v.recordNodeKey("ace", pub, "node.ace"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if on {
|
||||
v.LeaseHeard([]byte(`{"handed-to":"the operator's hand-over"}`), true, nil)
|
||||
} else {
|
||||
v.LeaseHeard([]byte(`{"instance":"novox/1/2026-10-27","epoch":57}`), true, nil)
|
||||
}
|
||||
return v, accounts, priv
|
||||
}
|
||||
|
||||
// 774/AC7 (novox/hq ADR 0561 §1, §2, §4): a want with no value, minting on, is minted once: kept whole in the controller's
|
||||
// shape as the module's broker, its hash in bus-accounts, said account-issued before secret-changed, and answered by own
|
||||
// sealed to the node; a value held or a hash adopted is never minted.
|
||||
func TestTheVaultMintsAWantedAccountOnceAndNothingItHolds(t *testing.T) {
|
||||
v, accounts, nodePriv := mintingVault(t, true)
|
||||
w := aWant("mod-x")
|
||||
before := len(v.bus.events)
|
||||
v.WantHeard(w.User, wantRaw(t, w), false)
|
||||
rec, held, err := v.Store.Read("own/ace/mod-x/broker")
|
||||
if err != nil || !held || rec.Generation != 1 || rec.Origin != OriginMade {
|
||||
t.Fatalf("the minted account is kept as %+v (held %v, %v)", rec, held, err)
|
||||
}
|
||||
value, err := seal.Open(rec.AtRest, v.Own.PrivateKey)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var got struct{ Password string }
|
||||
_ = json.Unmarshal([]byte(value), &got)
|
||||
shape, _ := buscompose.HeldCredential(w, got.Password)
|
||||
if len(got.Password) != 40 || value != string(shape) {
|
||||
t.Errorf("the minted value is %q, not the controller's shape %q", value, shape)
|
||||
}
|
||||
if !accounts.hashOf(w.User, got.Password, 1) {
|
||||
t.Errorf("%s's hash is not the minted password's: %+v", w.User, accounts.keys[w.User])
|
||||
}
|
||||
var order []string
|
||||
for _, e := range v.bus.events[before:] {
|
||||
order = append(order, e["event"].(string))
|
||||
}
|
||||
if strings.Join(order, ",") != AccountHashedEvent+","+AccountIssuedEvent+","+ChangedEvent {
|
||||
t.Errorf("said %v, want the hash, account-issued, then secret-changed", order)
|
||||
}
|
||||
for _, raw := range v.bus.raw[before:] {
|
||||
if strings.Contains(raw, got.Password) || strings.Contains(raw, "$2a$") {
|
||||
t.Errorf("a value or a hash was said: %s", raw)
|
||||
}
|
||||
}
|
||||
own, err := v.OwnOfNode("node.ace")
|
||||
if err != nil || len(own.Own) != 1 {
|
||||
t.Fatalf("own answered %+v, %v", own, err)
|
||||
}
|
||||
if opened, err := seal.Open(own.Own[0].Sealed, nodePriv); err != nil || opened != value {
|
||||
t.Errorf("own's sealed value opens to %q, %v", opened, err)
|
||||
}
|
||||
|
||||
// Heard again, or for a value held, or for a hash adopted: nothing is minted, nothing is held back.
|
||||
v.WantHeard(w.User, wantRaw(t, w), false)
|
||||
if rec, _, _ := v.Store.Read("own/ace/mod-x/broker"); rec.Generation != 1 {
|
||||
t.Errorf("a want heard again minted generation %d", rec.Generation)
|
||||
}
|
||||
accounts.keys["ace.mod-y"] = AccountHash{User: "ace.mod-y", Hash: "$2a$04$adopted", Adopted: true}
|
||||
y := aWant("mod-y")
|
||||
v.WantHeard(y.User, wantRaw(t, y), false)
|
||||
if _, held, _ := v.Store.Read("own/ace/mod-y/broker"); held || accounts.keys["ace.mod-y"].Hash != "$2a$04$adopted" {
|
||||
t.Errorf("an adopted user was minted")
|
||||
}
|
||||
if st := v.MintState(); len(st.HeldBack) != 0 || len(st.Minted) != 1 {
|
||||
t.Errorf("vault_status says %+v: an adopted user is not held back, one account minted", st)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package main
|
||||
|
||||
// wantGone is a wanted account's key deleted: forgotten here (its retirement is ADR 0230's, retire's).
|
||||
func (v *Vault) wantGone(user string) {
|
||||
v.minting.mu.Lock()
|
||||
defer v.minting.mu.Unlock()
|
||||
delete(v.minting.wants, user)
|
||||
delete(v.minting.heldBack, user)
|
||||
}
|
||||
|
||||
// wantBack is a wanted account's key written again.
|
||||
func (v *Vault) wantBack(string) {}
|
||||
@@ -58,6 +58,8 @@ type Vault struct {
|
||||
hashes hashing
|
||||
// adoption is the last adoption (adopt.go), under mu.
|
||||
adoption adoptionKept
|
||||
// minting is the switch on the lease and what the vault knows of the wanted bus accounts (mint.go).
|
||||
minting minting
|
||||
// callsMu guards pending: the keys of the calls open now (intake.go), never written anywhere.
|
||||
callsMu sync.Mutex
|
||||
pending map[string]*callKey
|
||||
@@ -106,8 +108,10 @@ func (v *Vault) keep(name, value, by, via, origin string) (Changed, error) {
|
||||
// was first made, and where it was moved from.
|
||||
type keepOptions struct {
|
||||
neverReplace bool
|
||||
madeAt time.Time
|
||||
movedFrom *MovedFrom
|
||||
// quiet keeps secret-changed for the caller to say once the rest is done (a minted account: mint.go).
|
||||
quiet bool
|
||||
madeAt time.Time
|
||||
movedFrom *MovedFrom
|
||||
// replaceOnly, keepPrevious and refuse are what move-in with replace_running keeps (owntake.go): only a value held is replaced,
|
||||
// keeping its origin and the value it replaces, and refuse, under the store's lock, says why a held value is not.
|
||||
replaceOnly bool
|
||||
@@ -208,7 +212,9 @@ func (v *Vault) keepLocked(name, value, by, via, origin string, opt keepOptions)
|
||||
return Changed{}, err
|
||||
}
|
||||
c := Changed{Name: name, Generation: rec.Generation, At: now, By: by}
|
||||
v.announce(c)
|
||||
if !opt.quiet {
|
||||
v.announce(c)
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -147,8 +147,9 @@ func TestNoBucketHoldsASecret(t *testing.T) {
|
||||
t.Errorf("the manifest declares buckets %s: a secret is in no bucket, sealed or not (ADR 0201 §13), and the one "+
|
||||
"bucket is the accounts' hashes (ADR 0547 clause 9)", s)
|
||||
}
|
||||
if r, _ := json.Marshal(manifest["reads"]); string(r) != `["mesh-controller.assignments","node-engine.have"]` {
|
||||
t.Errorf("the manifest reads %s: the vault reads the controller's assignments and the haves alone", r)
|
||||
if r, _ := json.Marshal(manifest["reads"]); string(r) != `["mesh-controller.assignments","node-engine.have","mesh-grants.accounts"]` {
|
||||
t.Errorf("the manifest reads %s: the vault reads the controller's assignments, the haves and the grants part's "+
|
||||
"wanted accounts alone", r)
|
||||
}
|
||||
sources, _ := filepath.Glob("*.go")
|
||||
bucket := regexp.MustCompile(`stdio\.State\(|nats-io|jetstream|KeyValue`)
|
||||
@@ -167,13 +168,15 @@ func TestNoBucketHoldsASecret(t *testing.T) {
|
||||
code = []byte(strings.Replace(string(code), "stdio.State(BusAccountsState).Put(", "", 1))
|
||||
code = []byte(strings.Replace(string(code), "stdio.State(BusAccountsState).Keys(", "", 1))
|
||||
}
|
||||
// The haves, in main.go alone: one read, one watch and their keys (own_drift), never a write; and one watch of
|
||||
// the assignments, which the vault's rules read from memory once it caught up (issue 1126).
|
||||
// The haves, in main.go alone: one read, one watch and their keys (own_drift), never a write; one watch of the
|
||||
// assignments, which the vault's rules read from memory once it caught up (issue 1126); and one watch of the wanted
|
||||
// accounts, the grants part's, which hold no secret (ADR 0561 §2).
|
||||
if f == "main.go" {
|
||||
code = []byte(strings.Replace(string(code), "stdio.State(AssignmentsRead).Watch(", "", 1))
|
||||
code = []byte(strings.Replace(string(code), "stdio.State(HaveRead).Get(", "", 1))
|
||||
code = []byte(strings.Replace(string(code), "stdio.State(HaveRead).Watch(", "", 1))
|
||||
code = []byte(strings.Replace(string(code), "stdio.State(HaveRead).Keys(", "", 1))
|
||||
code = []byte(strings.Replace(string(code), "stdio.State(WantedRead).Watch(", "", 1))
|
||||
}
|
||||
if m := bucket.Find(code); m != nil {
|
||||
t.Errorf("%s opens a bucket (%s): the vault keeps every secret in its own store only", f, m)
|
||||
|
||||
@@ -16,11 +16,13 @@
|
||||
"rotated",
|
||||
"secret-changed",
|
||||
"account-hashed",
|
||||
"own-replaced"
|
||||
"own-replaced",
|
||||
"account-issued"
|
||||
],
|
||||
"reads": [
|
||||
"mesh-controller.assignments",
|
||||
"node-engine.have"
|
||||
"node-engine.have",
|
||||
"mesh-grants.accounts"
|
||||
],
|
||||
"state": [
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user