A given own secret the module reads at start is held by nobody but that module, so the mesh need not read it to replace it: secret rotate now works on it, and a value given through secret accept is replaced on its own after the module's first good start under the mesh. Only a value an outside party issues (own-secrets "issued-by": "outside") or one the module applies stays as given, refused with the reason.
202 lines
8.6 KiB
Go
202 lines
8.6 KiB
Go
package inventory
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/novox/mesh-controller/internal/catalogue"
|
|
)
|
|
|
|
// A module with one secret of each kind the rule tells apart (novox/hq ADR 0228), assigned to the
|
|
// consumer machine.
|
|
func aModuleWithGivenSecrets(t *testing.T) (*Inventory, context.Context) {
|
|
t.Helper()
|
|
inv, ctx := twoNodesWithKeys(t)
|
|
m := catalogue.Manifest{Module: "letta", Version: "1", OwnSecrets: catalogue.OwnSecrets{
|
|
"server-password": {Path: "/var/lib/letta/server-password", Taken: catalogue.TakenAtStart},
|
|
"openai-api-key": {Path: "/var/lib/letta/openai-api-key", Taken: catalogue.TakenAtStart,
|
|
IssuedBy: catalogue.IssuedOutside},
|
|
"logflare": {Path: "/var/lib/letta/logflare", Taken: catalogue.TakenApplied},
|
|
"broker": {Path: "/var/lib/mesh/letta/broker"},
|
|
}}
|
|
if err := inv.RegisterModule(ctx, m, Source{}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
assign(t, inv, ctx, "consumer", "letta")
|
|
return inv, ctx
|
|
}
|
|
|
|
func assign(t *testing.T, inv *Inventory, ctx context.Context, node, module string) {
|
|
t.Helper()
|
|
n, err := inv.NodeByName(ctx, node)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := inv.store.Pool().Exec(ctx,
|
|
`insert into assignment (node, module) values ($1, $2) on conflict do nothing`, n.ID, module); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
// sent records a declaration sent to a machine now, as a push does, and answers its digest.
|
|
func sent(t *testing.T, inv *Inventory, ctx context.Context, node, digest string) string {
|
|
t.Helper()
|
|
n, err := inv.NodeByName(ctx, node)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := inv.RecordSent(ctx, n.ID, digest, nil); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return digest
|
|
}
|
|
|
|
func originOf(t *testing.T, inv *Inventory, ctx context.Context, node, module, name string) string {
|
|
t.Helper()
|
|
origin, _, err := inv.OwnSecretOrigin(ctx, node, module, name)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return origin
|
|
}
|
|
|
|
// **A given value is replaced once, after the first good start on it, and never again** (ADR 0228):
|
|
// not on a report of a declaration sent before it was given, not on a report of a declaration the
|
|
// mesh has moved past, and not a second time.
|
|
func TestAGivenValueIsReplacedAfterTheFirstGoodStartAndNeverAgain(t *testing.T) {
|
|
inv, ctx := aModuleWithGivenSecrets(t)
|
|
before := sent(t, inv, ctx, "consumer", "declaration-before")
|
|
|
|
marked, err := inv.AcceptGivenSecret(ctx, "consumer", "letta", "server-password", "typed-by-a-person")
|
|
if err != nil || !marked {
|
|
t.Fatalf("a given value for a secret the mesh may make is marked to be replaced: %v %v", marked, err)
|
|
}
|
|
// The machine's report of what it was sent before the value was given is not a start on it.
|
|
if got, err := inv.ReplaceGivenAfterStart(ctx, "consumer", before); err != nil || len(got) != 0 {
|
|
t.Fatalf("a start before the value was given replaced it: %+v %v", got, err)
|
|
}
|
|
carrying := sent(t, inv, ctx, "consumer", "declaration-carrying-it")
|
|
// A report about a declaration other than the one last sent says nothing about this one.
|
|
if got, err := inv.ReplaceGivenAfterStart(ctx, "consumer", before); err != nil || len(got) != 0 {
|
|
t.Fatalf("a report of an older declaration replaced it: %+v %v", got, err)
|
|
}
|
|
if originOf(t, inv, ctx, "consumer", "letta", "server-password") != OriginAccepted {
|
|
t.Fatal("the given value was replaced before its module started on it")
|
|
}
|
|
|
|
got, err := inv.ReplaceGivenAfterStart(ctx, "consumer", carrying)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(got) != 1 || got[0].Module != "letta" || got[0].Name != "server-password" ||
|
|
len(got[0].Machines) != 1 || got[0].Machines[0] != "consumer" {
|
|
t.Fatalf("the first good start replaced %+v", got)
|
|
}
|
|
if originOf(t, inv, ctx, "consumer", "letta", "server-password") != OriginMade {
|
|
t.Fatal("the replacement is not the mesh's own")
|
|
}
|
|
// Never again: the same report heard twice, and the next declaration's report.
|
|
if again, err := inv.ReplaceGivenAfterStart(ctx, "consumer", carrying); err != nil || len(again) != 0 {
|
|
t.Fatalf("the same start replaced it twice: %+v %v", again, err)
|
|
}
|
|
next := sent(t, inv, ctx, "consumer", "declaration-after")
|
|
if again, err := inv.ReplaceGivenAfterStart(ctx, "consumer", next); err != nil || len(again) != 0 {
|
|
t.Fatalf("a later start replaced the mesh's own value: %+v %v", again, err)
|
|
}
|
|
}
|
|
|
|
// **What stays as given** (ADR 0228): a value an outside party issued, a value the module applies,
|
|
// and a value the mesh's own code accepted — a bus account it issued is the mesh's word to a broker,
|
|
// and a fresh random value would break it.
|
|
func TestWhatIsNeverReplacedAfterAStart(t *testing.T) {
|
|
inv, ctx := aModuleWithGivenSecrets(t)
|
|
for _, name := range []string{"openai-api-key", "logflare"} {
|
|
marked, err := inv.AcceptGivenSecret(ctx, "consumer", "letta", name, "from-outside")
|
|
if err != nil || marked {
|
|
t.Fatalf("%s was marked to be replaced: %v %v", name, marked, err)
|
|
}
|
|
}
|
|
if err := inv.AcceptSecretForModule(ctx, "consumer", "letta", "broker", "issued-by-the-mesh"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
declared := sent(t, inv, ctx, "consumer", "declaration")
|
|
if got, err := inv.ReplaceGivenAfterStart(ctx, "consumer", declared); err != nil || len(got) != 0 {
|
|
t.Fatalf("a value that stays as given was replaced: %+v %v", got, err)
|
|
}
|
|
for _, name := range []string{"openai-api-key", "logflare", "broker"} {
|
|
if originOf(t, inv, ctx, "consumer", "letta", name) != OriginAccepted {
|
|
t.Fatalf("%s was touched", name)
|
|
}
|
|
}
|
|
// And asked by hand, the outside party's key is refused, saying why and how old it is.
|
|
var refused ErrNotRotatable
|
|
err := inv.RotateModuleSecret(ctx, "consumer", "letta", "openai-api-key")
|
|
if !errors.As(err, &refused) || !strings.Contains(err.Error(), "outside the mesh") ||
|
|
!strings.Contains(err.Error(), "day(s) ago") || !strings.Contains(err.Error(), "secret accept") {
|
|
t.Fatalf("rotating an outside party's key must be refused with its age and the way out: %v", err)
|
|
}
|
|
if originOf(t, inv, ctx, "consumer", "letta", "openai-api-key") != OriginAccepted {
|
|
t.Fatal("a refused rotation touched the value")
|
|
}
|
|
}
|
|
|
|
// On an adopted machine the module must be taken before a start is one under the mesh; and a module
|
|
// no longer assigned to the machine has no start to wait for.
|
|
func TestAGivenValueWaitsForTheTakeOnAnAdoptedMachine(t *testing.T) {
|
|
inv, ctx := aModuleWithGivenSecrets(t)
|
|
if err := inv.SetAdopted(ctx, "consumer", true); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := inv.AcceptGivenSecret(ctx, "consumer", "letta", "server-password", "the-found-one"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
held := sent(t, inv, ctx, "consumer", "declaration-holding-it")
|
|
if got, err := inv.ReplaceGivenAfterStart(ctx, "consumer", held); err != nil || len(got) != 0 {
|
|
t.Fatalf("a module held as found, not yet taken, had its given value replaced: %+v %v", got, err)
|
|
}
|
|
if err := inv.Take(ctx, "consumer", "letta"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
taken := sent(t, inv, ctx, "consumer", "declaration-taking-it")
|
|
got, err := inv.ReplaceGivenAfterStart(ctx, "consumer", taken)
|
|
if err != nil || len(got) != 1 {
|
|
t.Fatalf("the first good start after the take did not replace the given value: %+v %v", got, err)
|
|
}
|
|
|
|
// Unassigned: nothing starts, nothing is replaced.
|
|
if _, err := inv.AcceptGivenSecret(ctx, "consumer", "letta", "server-password", "given-again"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := inv.Unassign(ctx, "consumer", "letta"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
gone := sent(t, inv, ctx, "consumer", "declaration-without-it")
|
|
if got, err := inv.ReplaceGivenAfterStart(ctx, "consumer", gone); err != nil || len(got) != 0 {
|
|
t.Fatalf("a module no longer assigned had its given value replaced: %+v %v", got, err)
|
|
}
|
|
}
|
|
|
|
// A definition that changed after the value was given is asked again at the start: one that now says
|
|
// the value is an outside party's keeps it as given.
|
|
func TestADefinitionThatNowSaysOutsideKeepsTheGivenValue(t *testing.T) {
|
|
inv, ctx := aModuleWithGivenSecrets(t)
|
|
if _, err := inv.AcceptGivenSecret(ctx, "consumer", "letta", "server-password", "typed"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
m := catalogue.Manifest{Module: "letta", Version: "2", OwnSecrets: catalogue.OwnSecrets{
|
|
"server-password": {Path: "/var/lib/letta/server-password", Taken: catalogue.TakenAtStart,
|
|
IssuedBy: catalogue.IssuedOutside}}}
|
|
if err := inv.RegisterModule(ctx, m, Source{}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
declared := sent(t, inv, ctx, "consumer", "declaration")
|
|
if got, err := inv.ReplaceGivenAfterStart(ctx, "consumer", declared); err != nil || len(got) != 0 {
|
|
t.Fatalf("a value the definition now says is an outside party's was replaced: %+v %v", got, err)
|
|
}
|
|
if originOf(t, inv, ctx, "consumer", "letta", "server-password") != OriginAccepted {
|
|
t.Fatal("the value was touched")
|
|
}
|
|
}
|