Merge branch 'b9j/registry' into feat/registry-milestone
This commit is contained in:
+3
-159
@@ -1,126 +1,11 @@
|
|||||||
package buscompose
|
package buscompose
|
||||||
|
|
||||||
import (
|
import "time"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"slices"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// AccountsState is the grants part's state of wanted accounts, by bus user.
|
// AccountIssuedEvent is what the vault says when it minted an account: AccountIssued, never a value or a hash. The state of
|
||||||
const AccountsState = "accounts"
|
// wanted accounts it mints from is the grants part's (grantsstate.AccountsState; novox/hq ADR 0634 §1).
|
||||||
|
|
||||||
// GrantsAccounts is that state as a reader names it (`<owner>.<name>`).
|
|
||||||
const GrantsAccounts = GrantsModule + "." + AccountsState
|
|
||||||
|
|
||||||
// AccountIssuedEvent is what the vault says when it minted an account: AccountIssued, never a value or a hash.
|
|
||||||
const AccountIssuedEvent = "account-issued"
|
const AccountIssuedEvent = "account-issued"
|
||||||
|
|
||||||
// ClaimedSeat is one seat a module claims, as its runtime needs it (the controller's seatClaimed): the name, the scope (a
|
|
||||||
// node-scoped seat's verb carries the machine) and the verbs the runtime serves for it.
|
|
||||||
type ClaimedSeat struct {
|
|
||||||
Seat string `json:"seat"`
|
|
||||||
Scope string `json:"scope"`
|
|
||||||
Serves []string `json:"serves,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// WantedAccount is one key of the grants part's state `accounts`: an account the mesh needs, without its password.
|
|
||||||
type WantedAccount struct {
|
|
||||||
User string `json:"user"`
|
|
||||||
// Kind is KindModule (KindNodeTools for the runtime, named as a module is), KindProcess, KindProvisioner or
|
|
||||||
// KindLedger.
|
|
||||||
Kind Kind `json:"kind"`
|
|
||||||
Node string `json:"node"`
|
|
||||||
Module string `json:"module"`
|
|
||||||
// Process is a KindProcess account's process id.
|
|
||||||
Process string `json:"process,omitempty"`
|
|
||||||
// URL is the bus's address as that node reaches it, `nats://<host>:<port>`; Fingerprint the bus certificate's.
|
|
||||||
URL string `json:"url"`
|
|
||||||
Fingerprint string `json:"fingerprint,omitempty"`
|
|
||||||
// Claims are a module's claims with the verbs each seat promises: none for a process or a provisioner client.
|
|
||||||
Claims []ClaimedSeat `json:"claims,omitempty"`
|
|
||||||
// At is when the grants part wrote the key: not compared (Same).
|
|
||||||
At time.Time `json:"at"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// LedgersSecret is the tool runner's own secret holding its machine's ledger accounts, a map of module to credential
|
|
||||||
// (ADR 0597 §9).
|
|
||||||
const LedgersSecret = "ledgers"
|
|
||||||
|
|
||||||
// mintedKinds are the kinds of account the vault mints (ADR 0561 §1).
|
|
||||||
var mintedKinds = []Kind{KindModule, KindNodeTools, KindProcess, KindProvisioner, KindLedger}
|
|
||||||
|
|
||||||
// principal is the account as the composer names it.
|
|
||||||
func (w WantedAccount) principal() Principal {
|
|
||||||
return Principal{Kind: w.Kind, Node: w.Node, Module: w.Module, Process: w.Process}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Valid says why w is not an account the vault may mint, or nil: a kind it mints, every name a subject token, the user
|
|
||||||
// the one the composer derives from them, and an address to reach the bus at.
|
|
||||||
func (w WantedAccount) Valid() error {
|
|
||||||
if !slices.Contains(mintedKinds, w.Kind) {
|
|
||||||
return fmt.Errorf("%s is a %s account, which the vault never mints (ADR 0561 §4)", w.User, w.Kind)
|
|
||||||
}
|
|
||||||
for what, v := range map[string]string{"node": w.Node, "module": w.Module} {
|
|
||||||
if !safeSubject.MatchString(v) {
|
|
||||||
return fmt.Errorf("%s: the %s %q is not a name the mesh writes", w.User, what, v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if w.Kind == KindProcess && !safeSubject.MatchString(w.Process) {
|
|
||||||
return fmt.Errorf("%s: the process %q is not a name the mesh writes", w.User, w.Process)
|
|
||||||
}
|
|
||||||
if w.Kind != KindProcess && w.Process != "" {
|
|
||||||
return fmt.Errorf("%s: only a process's account names a process", w.User)
|
|
||||||
}
|
|
||||||
if want := w.principal().Username(); w.User != want {
|
|
||||||
return fmt.Errorf("%s is not the user of a %s account of %s on %s (that is %s)", w.User, w.Kind, w.Module, w.Node, want)
|
|
||||||
}
|
|
||||||
if !strings.HasPrefix(w.URL, "nats://") || len(w.URL) == len("nats://") {
|
|
||||||
return fmt.Errorf("%s names no bus address (url, nats://<host>:<port>)", w.User)
|
|
||||||
}
|
|
||||||
if len(w.Claims) > 0 && w.Kind != KindModule && w.Kind != KindNodeTools {
|
|
||||||
return fmt.Errorf("%s: a %s account holds no seat, so it carries no claims", w.User, w.Kind)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Same says whether two wanted accounts want the same account: every field but when it was written.
|
|
||||||
func (w WantedAccount) Same(o WantedAccount) bool {
|
|
||||||
a, b := w, o
|
|
||||||
a.At, b.At = time.Time{}, time.Time{}
|
|
||||||
ra, _ := json.Marshal(a)
|
|
||||||
rb, _ := json.Marshal(b)
|
|
||||||
return string(ra) == string(rb)
|
|
||||||
}
|
|
||||||
|
|
||||||
// OwnSecret is the module's own secret the account is held in: `broker`, `broker-<process id>` or `provisioner-broker`
|
|
||||||
// (the controller's names, ADR 0436 §2, ADR 0349 §11); a ledger account in the tool runner's `ledgers`, which keeps every
|
|
||||||
// ledger account of its machine, by module (ADR 0597 §9).
|
|
||||||
func (w WantedAccount) OwnSecret() string {
|
|
||||||
switch w.Kind {
|
|
||||||
case KindLedger:
|
|
||||||
return LedgersSecret
|
|
||||||
case KindProcess:
|
|
||||||
return "broker-" + w.Process
|
|
||||||
case KindProvisioner:
|
|
||||||
return "provisioner-broker"
|
|
||||||
}
|
|
||||||
return "broker"
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadWantedAccount reads the key user of the state: refused when it is not a valid wanted account of that user.
|
|
||||||
func ReadWantedAccount(user string, raw []byte) (WantedAccount, error) {
|
|
||||||
var w WantedAccount
|
|
||||||
if err := json.Unmarshal(raw, &w); err != nil {
|
|
||||||
return WantedAccount{}, fmt.Errorf("%s %s is not a wanted account: %w", GrantsAccounts, user, err)
|
|
||||||
}
|
|
||||||
if w.User != user {
|
|
||||||
return WantedAccount{}, fmt.Errorf("%s %s names the user %q", GrantsAccounts, user, w.User)
|
|
||||||
}
|
|
||||||
return w, w.Valid()
|
|
||||||
}
|
|
||||||
|
|
||||||
// AccountIssued is the vault's account-issued: which account it minted, and its generation. Never a value or a hash.
|
// AccountIssued is the vault's account-issued: which account it minted, and its generation. Never a value or a hash.
|
||||||
type AccountIssued struct {
|
type AccountIssued struct {
|
||||||
User string `json:"user"`
|
User string `json:"user"`
|
||||||
@@ -131,44 +16,3 @@ type AccountIssued struct {
|
|||||||
Generation int `json:"generation"`
|
Generation int `json:"generation"`
|
||||||
At time.Time `json:"at"`
|
At time.Time `json:"at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// HeldCredential is the value the account's holder reads, exactly as the controller builds it today (issueWith,
|
|
||||||
// sealProcess and sealProvisionerClient): a module's `{url, fingerprint, node, module, user, password, claims}`, a
|
|
||||||
// process's without claims, a provisioner client's without module or claims; an empty fingerprint or claims left out.
|
|
||||||
func HeldCredential(w WantedAccount, password string) ([]byte, error) {
|
|
||||||
if err := w.Valid(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if password == "" {
|
|
||||||
return nil, fmt.Errorf("%s: an account is held with its password", w.User)
|
|
||||||
}
|
|
||||||
switch w.Kind {
|
|
||||||
case KindProcess:
|
|
||||||
return json.Marshal(struct {
|
|
||||||
URL string `json:"url"`
|
|
||||||
Fingerprint string `json:"fingerprint,omitempty"`
|
|
||||||
Node string `json:"node"`
|
|
||||||
Module string `json:"module"`
|
|
||||||
User string `json:"user"`
|
|
||||||
Password string `json:"password"`
|
|
||||||
}{URL: w.URL, Fingerprint: w.Fingerprint, Node: w.Node, Module: w.Module, User: w.User, Password: password})
|
|
||||||
case KindProvisioner:
|
|
||||||
return json.Marshal(struct {
|
|
||||||
URL string `json:"url"`
|
|
||||||
Fingerprint string `json:"fingerprint,omitempty"`
|
|
||||||
Node string `json:"node"`
|
|
||||||
User string `json:"user"`
|
|
||||||
Password string `json:"password"`
|
|
||||||
}{URL: w.URL, Fingerprint: w.Fingerprint, Node: w.Node, User: w.User, Password: password})
|
|
||||||
}
|
|
||||||
return json.Marshal(struct {
|
|
||||||
URL string `json:"url"`
|
|
||||||
Fingerprint string `json:"fingerprint,omitempty"`
|
|
||||||
Node string `json:"node"`
|
|
||||||
Module string `json:"module"`
|
|
||||||
User string `json:"user"`
|
|
||||||
Password string `json:"password"`
|
|
||||||
Claims []ClaimedSeat `json:"claims,omitempty"`
|
|
||||||
}{URL: w.URL, Fingerprint: w.Fingerprint, Node: w.Node, Module: w.Module, User: w.User, Password: password,
|
|
||||||
Claims: w.Claims})
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -25,14 +25,10 @@ func TestTheBusNamesAgree(t *testing.T) {
|
|||||||
sn != "mesh.ledger.m.records.snapshot" {
|
sn != "mesh.ledger.m.records.snapshot" {
|
||||||
t.Fatalf("a ledger's subjects are %q and %q", h, sn)
|
t.Fatalf("a ledger's subjects are %q and %q", h, sn)
|
||||||
}
|
}
|
||||||
// 1966/AC23: a wanted account of kind ledger for m on a names the user ledger.a.m and the own secret ledgers.
|
// 1966/AC23: a ledger account for m on a is named ledger.a.m.
|
||||||
ledger := WantedAccount{User: "ledger.a.m", Kind: KindLedger, Node: "a", Module: "m", URL: "nats://10.0.0.1:4222"}
|
|
||||||
if got := (Principal{Kind: KindLedger, Node: "a", Module: "m"}).Username(); got != "ledger.a.m" {
|
if got := (Principal{Kind: KindLedger, Node: "a", Module: "m"}).Username(); got != "ledger.a.m" {
|
||||||
t.Fatalf("a ledger account is named %q", got)
|
t.Fatalf("a ledger account is named %q", got)
|
||||||
}
|
}
|
||||||
if err := ledger.Valid(); err != nil || ledger.OwnSecret() != "ledgers" {
|
|
||||||
t.Fatalf("a wanted ledger account: %v, held in %q", err, ledger.OwnSecret())
|
|
||||||
}
|
|
||||||
// A seat's family is the shared events stream filtered to its subjects: a definition says the stream's age.
|
// A seat's family is the shared events stream filtered to its subjects: a definition says the stream's age.
|
||||||
for _, st := range MeshStreams() {
|
for _, st := range MeshStreams() {
|
||||||
if st.Name == EventsStream && int64(st.MaxAge) != seatdef.SharedEventsMaxAgeDays*24*60*60 {
|
if st.Name == EventsStream && int64(st.MaxAge) != seatdef.SharedEventsMaxAgeDays*24*60*60 {
|
||||||
|
|||||||
@@ -1,19 +1,5 @@
|
|||||||
package catalogue
|
package catalogue
|
||||||
|
|
||||||
// DeliverySeat is the seat mesh-delivery holds.
|
|
||||||
|
|
||||||
// RemovedDeliveryVerbs are the delivery seat's verbs release 6 removed (novox/hq ADR 0539 clause 17): with no plan the
|
|
||||||
// gate keeps the merge's states and derives nothing after the merge. Each stays in deliveryVerbs as an optional row
|
|
||||||
// with no input, so the holder of before and the holder of release 6 both hold the seat and a caller is answered by
|
|
||||||
// name with its replacement, given here.
|
|
||||||
var RemovedDeliveryVerbs = map[string]string{
|
|
||||||
"times": "the delivery view (nox-delivery): each machine's want and have, and since when",
|
|
||||||
"what-if": "the merge check's composition, which composes every machine a change touches",
|
|
||||||
"release": "the assigner's asks (hold, restore, backup, record), answered on the phone or in the delivery view",
|
|
||||||
"stop": "a bad fingerprint is held and its canary set back by the assigner (ADR 0429 §6, ADR 0539 clause 18)",
|
|
||||||
"retire-history": "nothing to retire: the gate keeps no delivery after its merge",
|
|
||||||
}
|
|
||||||
|
|
||||||
// SeatSays is whether a consumed `<seat>.<event>` names one of the seat's own events: the seat emits it,
|
// SeatSays is whether a consumed `<seat>.<event>` names one of the seat's own events: the seat emits it,
|
||||||
// exactly or by one of its patterns (`log.*`). A consumed name whose emitter is a seat and whose event the
|
// exactly or by one of its patterns (`log.*`). A consumed name whose emitter is a seat and whose event the
|
||||||
// seat does not emit is the event of the module of that name (novox/hq ADR 0239): mesh-delivery is a seat
|
// seat does not emit is the event of the module of that name (novox/hq ADR 0239): mesh-delivery is a seat
|
||||||
|
|||||||
@@ -19,9 +19,9 @@ func TestTheDeliverySeatHasNoPlanVerbs(t *testing.T) {
|
|||||||
}
|
}
|
||||||
for _, name := range []string{"times", "what-if", "release", "stop", "retire-history"} {
|
for _, name := range []string{"times", "what-if", "release", "stop", "retire-history"} {
|
||||||
v, ok := rows[name]
|
v, ok := rows[name]
|
||||||
replacement, removed := RemovedDeliveryVerbs[name]
|
replacement := strings.TrimPrefix(v.Description, "Removed in R6 (ADR 0539): refused by name, naming its replacement: ")
|
||||||
if !ok || !removed || replacement == "" {
|
if !ok || replacement == "" || replacement == v.Description {
|
||||||
t.Errorf("%s: in the table %v, in the removed list %v", name, ok, removed)
|
t.Errorf("%s: in the table %v, replacement %q", name, ok, replacement)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if !strings.HasPrefix(v.Description, "Removed in R6 (ADR 0539): refused by name") || !strings.Contains(v.Description, replacement) || !v.Optional {
|
if !strings.HasPrefix(v.Description, "Removed in R6 (ADR 0539): refused by name") || !strings.Contains(v.Description, replacement) || !v.Optional {
|
||||||
|
|||||||
Reference in New Issue
Block a user