mayIssue no longer requires a module's own broker secret when one of its processes declares bus: mesh-mail's real manifest has none, its tools served by the tool runner. rollout mint gained a KindProcess case, so a process's account rotates the same way every other bus account does. The release's acceptance test now composes its principals the real way (inventory.ProcessesOf -> broker.Users -> broker.ComposeAccounts), mints through the store as module issue does, and sets each connection's own inbox prefix so replies are not refused; it moved from internal/broker (which cannot import inventory) to cmd/mesh-controller. Added an integration test proving the mint, accept and vault hand-off, and that issuing again rotates the password.
107 lines
4.3 KiB
Go
107 lines
4.3 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/novox/mesh-controller/internal/broker"
|
|
"github.com/novox/mesh-controller/internal/catalogue"
|
|
)
|
|
|
|
// mailSenderWithProcess is mesh-mail's real manifest shape (novox/hq ADR 0436 §7, §8, the lead's fixture):
|
|
// no own secret named `broker` at all — its tools run in the machine's tool runner — and the process "send"
|
|
// declares bus, whose own secret broker-send is where module issue delivers its account (ADR 0436 §2),
|
|
// owned by mesh-mail-send, the user resource the process declares, at mode 0600 as every own secret is.
|
|
func mailSenderWithProcess() catalogue.Manifest {
|
|
m, err := catalogue.ParseManifest([]byte(`{"module":"mesh-mail","version":"1","emits":["mail.send.sent"],
|
|
"own-secrets":{"broker-send":{"path":"/var/lib/mesh-mail/broker-send","owner":"mesh-mail-send"}},
|
|
"resources":[{"id":"account","type":"user","name":"mesh-mail-send"},
|
|
{"id":"send","type":"process","run":["./mesh-mail","send"],"user":"mesh-mail-send",
|
|
"bus":{"emits":["mail.send.sent"]}}]}`))
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return m
|
|
}
|
|
|
|
// review round 1, finding 1 (1181): a module declaring no `broker` of its own at all — mesh-mail's real
|
|
// shape, tools served by the tool runner — still passes mayIssue on its process's bus declaration alone.
|
|
func TestAModuleWithNoBrokerAndOnlyAProcessMayBeIssued(t *testing.T) {
|
|
if err := mayIssue(mailSenderWithProcess()); err != nil {
|
|
t.Fatalf("a module with no broker of its own, whose process declares bus, was refused: %v", err)
|
|
}
|
|
}
|
|
|
|
// 1181/AC1: a process with a bus declaration and no own secret broker-<process id> cannot be issued — the
|
|
// same refusal mayIssue already gives a module with no `broker` (novox/hq ADR 0436 §2).
|
|
func TestAProcessWithNoBrokerSecretCannotBeIssued(t *testing.T) {
|
|
m := mailSenderWithProcess()
|
|
delete(m.OwnSecrets, "broker-send")
|
|
err := mayIssue(m)
|
|
if err == nil {
|
|
t.Fatal("a process with no broker-<id> own secret was issued an account")
|
|
}
|
|
for _, want := range []string{"mesh-mail", "send", "broker-send", "own-secrets"} {
|
|
if !strings.Contains(err.Error(), want) {
|
|
t.Errorf("the refusal does not say %q: %v", want, err)
|
|
}
|
|
}
|
|
if err := mayIssue(mailSenderWithProcess()); err != nil {
|
|
t.Errorf("a process declaring its own broker-<id> secret was refused: %v", err)
|
|
}
|
|
}
|
|
|
|
// 1181/AC1: the account held for a process is the provisioner client's form — {url, fingerprint, node,
|
|
// module, user, password} — named for the process, never the module's own account.
|
|
func TestAProcesssHeldAccountIsTheProvisionersForm(t *testing.T) {
|
|
user := broker.Principal{Kind: broker.KindProcess, Node: "novox", Module: "mesh-mail", Process: "send"}.Username()
|
|
if user != "novox.mesh-mail.send" {
|
|
t.Fatalf("the process's username: %q", user)
|
|
}
|
|
held, err := heldProcessAccount(broker.Broker{Fingerprint: "abc"}, "novox.example:4222", "novox", "mesh-mail",
|
|
user, "a-password")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var got map[string]any
|
|
if err := json.Unmarshal(held, &got); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
want := map[string]any{"url": "nats://novox.example:4222", "fingerprint": "abc", "node": "novox",
|
|
"module": "mesh-mail", "user": "novox.mesh-mail.send", "password": "a-password"}
|
|
for k, v := range want {
|
|
if got[k] != v {
|
|
t.Errorf("%s: %v, want %v", k, got[k], v)
|
|
}
|
|
}
|
|
if len(got) != len(want) {
|
|
t.Errorf("the held account carries %v; want exactly %v (never claims — a process holds no seat)", got, want)
|
|
}
|
|
}
|
|
|
|
// 1181/AC1: composed, the process's own secret belongs to its own account at 0600 (novox/hq ADR 0436 §1, §2;
|
|
// the owner mechanism is issue 1170's, proven generically there — this is mesh-mail's own shape).
|
|
func TestAProcessesOwnSecretIsOwnedByItsOwnAccount(t *testing.T) {
|
|
m := mailSenderWithProcess()
|
|
r := catalogue.Resolution{Node: "novox", Modules: []catalogue.Manifest{m}}
|
|
out, err := r.Declaration(catalogue.Rendering{
|
|
Needed: map[string]map[string]string{"mesh-mail": {"broker-send": "sealed-account"}},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("composing the declaration: %v", err)
|
|
}
|
|
var found bool
|
|
for _, res := range out {
|
|
if res["type"] == "file" && res["path"] == "/var/lib/mesh-mail/broker-send" {
|
|
found = true
|
|
if got := res["owner"]; got != "mesh-mail-send" {
|
|
t.Fatalf("broker-send's owner: %v; want mesh-mail-send", got)
|
|
}
|
|
}
|
|
}
|
|
if !found {
|
|
t.Fatal("broker-send was not composed at all")
|
|
}
|
|
}
|