Issue a bus process's account when its module is assigned or gains it, and warn of one still missing instead of composing it silently (issue 1226) #427
@@ -233,6 +233,16 @@ func prepareSend(ctx context.Context, open *stores, node string) ([]string, erro
|
||||
if !slices.Contains(in.BusMinted, "provisioner."+node) {
|
||||
continue
|
||||
}
|
||||
default:
|
||||
// A bus process's own account is minted by `issue`, never made as a random value (issue 1226).
|
||||
if err := processAccountRefusal(m, node, name, in.BusMinted); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if strings.HasPrefix(name, "broker-") && len(m.ProcessBuses()) > 0 {
|
||||
if _, isProcess := m.ProcessBuses()[strings.TrimPrefix(name, "broker-")]; isProcess {
|
||||
continue // issued, and not held under this key: issued again, never made
|
||||
}
|
||||
}
|
||||
}
|
||||
if _, err := inv.SecretForModule(ctx, node, m.Module, name); err == nil {
|
||||
did = true
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
|
||||
"github.com/novox/mesh-controller/internal/broker"
|
||||
"github.com/novox/mesh-controller/internal/inventory"
|
||||
)
|
||||
|
||||
// processUser is the bus user of one process of a module on a node (novox/hq ADR 0436 §1).
|
||||
func processUser(node, module, process string) string {
|
||||
return broker.Principal{Kind: broker.KindProcess, Node: node, Module: module, Process: process}.Username()
|
||||
}
|
||||
|
||||
// processAccountRefusal is the refusal for the own secret broker-<process id> of a process that declares bus when
|
||||
// nobody issued its account (novox/hq ADR 0436 §2, issue 1226): the account is never invented as a random value
|
||||
// the process cannot read, it is minted by `mesh-controller.issue`. Nil for any other secret, and for an account
|
||||
// that is issued.
|
||||
func processAccountRefusal(m catalogue.Manifest, node, secret string, minted []string) error {
|
||||
id, isProcess := strings.CutPrefix(secret, "broker-")
|
||||
if !isProcess {
|
||||
return nil
|
||||
}
|
||||
if _, declared := m.ProcessBuses()[id]; !declared {
|
||||
return nil
|
||||
}
|
||||
user := processUser(node, m.Module, id)
|
||||
for _, u := range minted {
|
||||
if u == user {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("%s on %s has no account for its process %s: nothing was issued for %s, and a push would "+
|
||||
"seal a random value its process cannot read (novox/hq ADR 0436 §2, issue 1226). "+
|
||||
"`mesh-controller.issue %s %s` (module issue %s --node %s), then push again",
|
||||
m.Module, node, id, user, node, m.Module, m.Module, node)
|
||||
}
|
||||
|
||||
// processAccountsMissing is each process of m that declares bus and whose account is not minted yet on node.
|
||||
func processAccountsMissing(m catalogue.Manifest, node string, minted []string) []broker.ProcessPrincipal {
|
||||
have := map[string]bool{}
|
||||
for _, u := range minted {
|
||||
have[u] = true
|
||||
}
|
||||
var out []broker.ProcessPrincipal
|
||||
for _, pr := range inventory.ProcessesOf(m) {
|
||||
if !have[processUser(node, m.Module, pr.Process)] {
|
||||
out = append(out, pr)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// issueMissingProcessAccounts issues the accounts of m's bus processes on node that are not minted, and no
|
||||
// other: an account already issued is rotated on purpose, never by a later delivery or assignment (issue 1226).
|
||||
// It says what it issued, one line, and nothing when nothing was missing.
|
||||
func issueMissingProcessAccounts(ctx context.Context, inv *inventory.Inventory, m catalogue.Manifest, node string) (string, error) {
|
||||
if len(m.ProcessBuses()) == 0 || mayIssue(m) != nil {
|
||||
return "", nil
|
||||
}
|
||||
minted, err := inv.BusMinted(ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
missing := processAccountsMissing(m, node, minted)
|
||||
if len(missing) == 0 {
|
||||
return "", nil
|
||||
}
|
||||
known, err := broker.FromEnvironment()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("cannot deliver a credential without knowing where the bus is: %w", err)
|
||||
}
|
||||
reachable, err := brokerReachableAt(ctx, inv, known, node)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
busAddress, err := broker.BusAddress()
|
||||
if err != nil {
|
||||
busAddress = ""
|
||||
}
|
||||
var ids []string
|
||||
for _, pr := range missing {
|
||||
if err := issueProcess(ctx, inv, m, pr, node, busAddress, known, reachable); err != nil {
|
||||
return "", err
|
||||
}
|
||||
ids = append(ids, pr.Process)
|
||||
}
|
||||
return fmt.Sprintf("its process account(s) %s issued and sealed to %s, and arrive with the push",
|
||||
strings.Join(ids, ", "), node), nil
|
||||
}
|
||||
|
||||
// issueGainedProcessAccounts runs after a module's new definition is registered: on each machine assigned it, the
|
||||
// accounts of its bus processes not yet minted are issued, so a process a delivery adds is not pushed with a
|
||||
// random stand-in (issue 1226). Never fatal: the build worked and the module is registered, so a failure is said,
|
||||
// and the push refuses the module until `mesh-controller.issue` is run.
|
||||
func issueGainedProcessAccounts(ctx context.Context, inv *inventory.Inventory, m catalogue.Manifest) {
|
||||
if len(m.ProcessBuses()) == 0 {
|
||||
return
|
||||
}
|
||||
nodes, err := inv.Running(ctx, m.Module)
|
||||
if err != nil {
|
||||
fmt.Printf("%s: its machines are not read to issue its process accounts: %v\n", m.Module, err)
|
||||
return
|
||||
}
|
||||
for _, node := range nodes {
|
||||
line, err := issueMissingProcessAccounts(ctx, inv, m, node)
|
||||
if err != nil {
|
||||
fmt.Printf("%s on %s: its process accounts are not issued (%v): `mesh-controller.issue %s %s` first\n",
|
||||
m.Module, node, err, node, m.Module)
|
||||
continue
|
||||
}
|
||||
if line != "" {
|
||||
fmt.Printf("%s on %s: %s\n", m.Module, node, line)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// 1226/AC1: given a module whose process declares bus and has no issued account, when a machine's declaration
|
||||
// is composed, the push refuses that module, naming issue. (The composition's wiring is proven against the store
|
||||
// in TestAGainedProcessIsIssuedWhenTheDeliveryRecordsIt.)
|
||||
func TestAProcessAccountNobodyIssuedIsRefusedNamingIssue(t *testing.T) {
|
||||
m := mailSenderWithProcess()
|
||||
err := processAccountRefusal(m, "laptop", "broker-send", nil)
|
||||
if err == nil {
|
||||
t.Fatal("1226/AC1: a process account nobody issued was not refused, so a random value would be made")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "mesh-controller.issue laptop mesh-mail") {
|
||||
t.Fatalf("1226/AC1: the refusal does not name issue: %v", err)
|
||||
}
|
||||
// Other names are not this rule's: the module's own broker and a secret of no process.
|
||||
if processAccountRefusal(m, "laptop", "broker", nil) != nil || processAccountRefusal(m, "laptop", "broker-other", nil) != nil {
|
||||
t.Fatal("1226/AC1: a name that is no declared process's account was refused")
|
||||
}
|
||||
// Issued, it is not refused.
|
||||
if err := processAccountRefusal(m, "laptop", "broker-send", []string{"laptop.mesh-mail.send"}); err != nil {
|
||||
t.Fatalf("1226/AC1: an issued account was refused: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// 1226/AC3: an account already issued is not replaced by a later compose, nor issued again by a delivery.
|
||||
func TestAnIssuedProcessAccountIsNotIssuedAgain(t *testing.T) {
|
||||
m := mailSenderWithProcess()
|
||||
if got := processAccountsMissing(m, "laptop", nil); len(got) != 1 || got[0].Process != "send" {
|
||||
t.Fatalf("1226/AC3: an account not issued is not missing: %v", got)
|
||||
}
|
||||
if got := processAccountsMissing(m, "laptop", []string{"laptop.mesh-mail.send"}); len(got) != 0 {
|
||||
t.Fatalf("1226/AC3: an issued account would be issued again: %v", got)
|
||||
}
|
||||
if got := processAccountsMissing(m, "ace", []string{"laptop.mesh-mail.send"}); len(got) != 1 {
|
||||
t.Fatalf("1226/AC3: another machine's account stands in for this one's: %v", got)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user