Files
mesh-catalog/modules/mesh-vault/cmd/mesh-vault/ledgers.go
T

146 lines
5.5 KiB
Go

package main
// The ledger accounts of a machine and the tool runner's own secret `ledgers` (novox/hq ADR 0597 §9, design 25 § 12.4,
// issue 1966 V1 and V2). Nothing here is the custody ledger of ledger.go (ADR 0085): this file is about the bus account
// of kind ledger, one per module assignment that asks for a ledger.
//
// The tool runner of a machine holds its machine's ledger accounts together as its own secret `ledgers`
// (`own/<node>/node-tools/ledgers`): a JSON map of module to credential, `{}` when there is none, so its composition
// never refuses a secret the vault did not make. The vault makes it as `{}` for every machine whose tool runner is
// wanted, and rewrites it on each ledger account minted or retired, saying `secret-changed` once per rewrite.
import (
"encoding/json"
"fmt"
"sort"
"strings"
"git.novox.be/novox/mesh-sdk/go/buscompose"
)
// toolRunnerModule is the module whose own secret `ledgers` is: the tool runner (mesh-tools node-tools).
const toolRunnerModule = "node-tools"
// ledgersSecret is the vault's name of node's `ledgers`.
func ledgersSecret(node string) (string, error) {
return OwnSecret(node, toolRunnerModule, buscompose.LedgersSecret)
}
// isLedgersSecret says secret is a machine's `ledgers`: `own/<node>/node-tools/ledgers`.
func isLedgersSecret(secret string) bool {
parts := strings.Split(secret, "/")
return len(parts) == 4 && parts[0] == "own" && parts[2] == toolRunnerModule && parts[3] == buscompose.LedgersSecret
}
// toolRunnerNodes are the machines whose tool runner is wanted, sorted.
func (v *Vault) toolRunnerNodes() []string {
v.minting.mu.Lock()
defer v.minting.mu.Unlock()
var out []string
for _, w := range v.minting.wants {
if w.Kind == buscompose.KindNodeTools {
out = append(out, w.Node)
}
}
sort.Strings(out)
return out
}
// LedgersPass makes `ledgers` as `{}` on every machine whose tool runner is wanted and for which the vault holds none,
// at the vault's minute pass (and when the tool runner's want is heard).
func (v *Vault) LedgersPass() {
for _, node := range v.toolRunnerNodes() {
v.ensureLedgers(node)
}
}
// ensureLedgers keeps node's `ledgers` as `{}` when the vault holds none: a held one is never touched here.
func (v *Vault) ensureLedgers(node string) {
secret, err := ledgersSecret(node)
if err != nil {
v.log("%s's ledgers are not made: %v", node, err)
return
}
_, held, err := v.Store.Read(secret)
if err != nil || held {
if err != nil {
v.log("%s is not read, so its ledgers are not made: %v", secret, err)
}
return
}
empty, _ := json.Marshal(map[string]json.RawMessage{})
if _, err := v.keepLocked(secret, string(empty), "the vault", "", OriginMade, keepOptions{neverReplace: true}); err != nil {
v.log("%s is not made, tried again at the next pass: %v", secret, err)
return
}
v.log("made %s as {}: no ledger account is held for its machine yet", secret)
}
// ledgerAccountPrefix starts the name a ledger account is kept under: `ledger/<machine>/<module>`. It is the account's
// own record, never an own secret (so `own` hands no module its ledger account); the machine's `ledgers` is made of them.
const ledgerAccountPrefix = "ledger/"
// LedgerAccountSecret is the vault's name of the credential of the ledger account of module on node.
func LedgerAccountSecret(node, module string) (string, error) {
for what, v := range map[string]string{"the machine": node, "the module": module} {
if !namePart.MatchString(v) {
return "", fmt.Errorf("%s %q is not a name the mesh writes", what, v)
}
}
return ledgerAccountPrefix + node + "/" + module, nil
}
// ledgerAccountOf says secret is a ledger account's name, with its machine and module.
func ledgerAccountOf(secret string) (node, module string, ok bool) {
parts := strings.Split(secret, "/")
if len(parts) != 3 || parts[0] != "ledger" || !namePart.MatchString(parts[1]) || !namePart.MatchString(parts[2]) {
return "", "", false
}
return parts[1], parts[2], true
}
// rewriteLedgers makes node's `ledgers` of the ledger accounts that stand: held, not retired, and wanted (once the wanted
// accounts are read whole; before that every one held stands, so a start never drops what it has not heard yet). It
// says `secret-changed` only when the map changed.
func (v *Vault) rewriteLedgers(node string) {
v.minting.ledgersMu.Lock()
defer v.minting.ledgersMu.Unlock()
secret, err := ledgersSecret(node)
if err != nil {
v.log("%s's ledgers are not rewritten: %v", node, err)
return
}
records, err := v.Store.ListPrefix(ledgerAccountPrefix + node + "/")
if err != nil {
v.log("%s is not rewritten, the ledger accounts of %s could not be read: %v", secret, node, err)
return
}
v.minting.mu.Lock()
read := v.minting.retiring.wantsRead
v.minting.mu.Unlock()
standing := map[string]json.RawMessage{}
for _, r := range records {
_, module, ok := ledgerAccountOf(r.Name)
if !ok || r.Retired != nil {
continue
}
if _, wanted := v.wanted("ledger." + node + "." + module); read && !wanted {
continue
}
value, err := openAtRest(r, v)
if err != nil || !BusAccountValue(r.Name, value) {
v.log("%s is not an account the vault can open, so it is left out of %s", r.Name, secret)
continue
}
standing[module] = json.RawMessage(value)
}
raw, err := json.Marshal(standing)
if err != nil {
v.log("%s is not rewritten: %v", secret, err)
return
}
if _, err := v.keepLocked(secret, string(raw), "the vault", "", OriginMade, keepOptions{}); err != nil {
v.log("%s is NOT REWRITTEN (%d ledger accounts stand): %v", secret, len(standing), err)
}
}