package inventory import ( "testing" "github.com/novox/mesh-controller/internal/catalogue" ) // A value a provider's software issued is sealed to every pair that holds the provision from that provider, under // one stamp of the value, and the same value given again writes nothing and sends nobody (novox/hq ADR 0305 §4, // §5, issue 526). func TestAnIssuedValueIsSealedToEveryHolderOnceAndNotAgain(t *testing.T) { inv, ctx := twoNodesWithKeys(t) consumerKey, consumerOpens := aSealingKey(t) cn, _ := inv.NodeByName(ctx, "consumer") if err := inv.RecordSealingKey(ctx, cn.ID, consumerKey); err != nil { t.Fatal(err) } for _, m := range []string{"kometa", "tautulli"} { if err := inv.RegisterModule(ctx, catalogue.Manifest{Module: m, Version: "1", Requires: []string{"plex-api"}, Secrets: map[string]string{"plex-api": "/run/plex-api.secret"}}, Source{}); err != nil { t.Fatal(err) } // What the mesh made before the provider gave its value: a random value per pair (issue 164). if _, err := inv.SecretFor(ctx, "plex-api", "consumer", m, "provider", ""); err != nil { t.Fatal(err) } } kept, err := inv.KeepIssuedSecret(ctx, "plex-api", "provider", "the-servers-own-token") if err != nil { t.Fatal(err) } if !kept.Changed || len(kept.Holders) != 2 { t.Fatalf("the value was not sealed to both holders: %+v", kept) } if len(kept.Machines) != 2 || kept.Machines[0] != "consumer" || kept.Machines[1] != "provider" { t.Fatalf("the machines to send are the provider's and every holder's: %v", kept.Machines) } for _, m := range []string{"kometa", "tautulli"} { got, err := inv.SecretFor(ctx, "plex-api", "consumer", m, "provider", "") if err != nil { t.Fatal(err) } if got.Origin != OriginIssued { t.Fatalf("%s's pair says it came from %q, not the provider's software", m, got.Origin) } opened, ok := consumerOpens(got.ForConsumer) if !ok || string(opened) != "the-servers-own-token" { t.Fatalf("%s's copy is not the provider's value", m) } var generation string pn, _ := inv.NodeByName(ctx, "provider") if err := inv.store.Pool().QueryRow(ctx, `select generation from secret where name = 'plex-api' and consumer = $1 and consumer_module = $2 and provider = $3`, cn.ID, m, pn.ID).Scan(&generation); err != nil { t.Fatal(err) } if generation != IssuedStamp("the-servers-own-token") { t.Fatalf("%s's pair is not stamped with the value's fingerprint: %q", m, generation) } } before, _ := inv.SecretFor(ctx, "plex-api", "consumer", "kometa", "provider", "") // A consumer that binds later gets a pair of its own, and no holder's value is remade for it: the offer names // no own secret, so ADR 0158 §6's remaking on a bind does not reach it (ADR 0305 §7). if err := inv.RegisterModule(ctx, catalogue.Manifest{Module: "overseerr", Version: "1", Requires: []string{"plex-api"}, Secrets: map[string]string{"plex-api": "/run/plex-api.secret"}}, Source{}); err != nil { t.Fatal(err) } late, err := inv.SecretFor(ctx, "plex-api", "consumer", "overseerr", "provider", "") if err != nil { t.Fatal(err) } if late.Origin == OriginIssued { t.Fatal("a consumer that bound after the ask holds the issued value without an ask") } if held, _ := inv.SecretFor(ctx, "plex-api", "consumer", "kometa", "provider", ""); held.ForConsumer != before.ForConsumer { t.Fatal("a later bind remade a holder's issued value") } // The next ask seals the provider's value to the late consumer alone; the holders that had it are not touched. again, err := inv.KeepIssuedSecret(ctx, "plex-api", "provider", "the-servers-own-token") if err != nil { t.Fatal(err) } if !again.Changed || len(again.Sealed) != 1 || again.Sealed[0].ConsumerModule != "overseerr" { t.Fatalf("the late consumer was not sealed to, or not alone: %+v", again) } if held, _ := inv.SecretFor(ctx, "plex-api", "consumer", "kometa", "provider", ""); held.ForConsumer != before.ForConsumer { t.Fatal("sealing the late consumer changed what a holder already held") } if got, _ := inv.SecretFor(ctx, "plex-api", "consumer", "overseerr", "provider", ""); got.Origin != OriginIssued { t.Fatal("the late consumer does not hold the issued value after the ask") } // And once every holder holds it, the same value writes nothing and sends nobody. still, err := inv.KeepIssuedSecret(ctx, "plex-api", "provider", "the-servers-own-token") if err != nil { t.Fatal(err) } if still.Changed || len(still.Machines) != 0 { t.Fatalf("the same value was sealed again, which restarts every consumer for nothing: %+v", still) } after, _ := inv.SecretFor(ctx, "plex-api", "consumer", "kometa", "provider", "") if after.ForConsumer != before.ForConsumer { t.Fatal("the same value given again changed what the consumer holds") } changed, err := inv.KeepIssuedSecret(ctx, "plex-api", "provider", "a-token-plex-changed") if err != nil { t.Fatal(err) } if !changed.Changed { t.Fatal("a changed value was not sealed") } got, _ := inv.SecretFor(ctx, "plex-api", "consumer", "tautulli", "provider", "") if opened, ok := consumerOpens(got.ForConsumer); !ok || string(opened) != "a-token-plex-changed" { t.Fatal("the changed value did not reach the holder") } if _, err := inv.KeepIssuedSecret(ctx, "plex-api", "provider", " "); err == nil { t.Fatal("an empty value was kept") } } // A consumer whose sealing key changed holds a value the mesh made in place of the issued one, and the next ask // seals the issued value to it again; so does a pair a person accepted over it (novox/hq ADR 0305 §5, the review // of mesh-controller #239). func TestAnIssuedValueIsSealedAgainAfterAKeyChangeOrAnAcceptance(t *testing.T) { inv, ctx := twoNodesWithKeys(t) if err := inv.RegisterModule(ctx, catalogue.Manifest{Module: "kometa", Version: "1", Requires: []string{"plex-api"}, Secrets: map[string]string{"plex-api": "/run/plex-api.secret"}}, Source{}); err != nil { t.Fatal(err) } if _, err := inv.SecretFor(ctx, "plex-api", "consumer", "kometa", "provider", ""); err != nil { t.Fatal(err) } if kept, err := inv.KeepIssuedSecret(ctx, "plex-api", "provider", "the-servers-own-token"); err != nil || !kept.Changed { t.Fatalf("the first ask did not seal: %+v %v", kept, err) } // The consumer rejoins with a new key: the mesh remakes the pair, a value the software refuses. newKey, opens := aSealingKey(t) cn, _ := inv.NodeByName(ctx, "consumer") if err := inv.RecordSealingKey(ctx, cn.ID, newKey); err != nil { t.Fatal(err) } remade, err := inv.SecretFor(ctx, "plex-api", "consumer", "kometa", "provider", "") if err != nil { t.Fatal(err) } if remade.Origin != OriginMade { t.Fatalf("a value the mesh remade says it came from %q", remade.Origin) } again, err := inv.KeepIssuedSecret(ctx, "plex-api", "provider", "the-servers-own-token") if err != nil { t.Fatal(err) } if !again.Changed || len(again.Sealed) != 1 { t.Fatalf("the issued value was not sealed again after the key changed: %+v", again) } got, _ := inv.SecretFor(ctx, "plex-api", "consumer", "kometa", "provider", "") if opened, ok := opens(got.ForConsumer); !ok || string(opened) != "the-servers-own-token" { t.Fatal("the consumer does not hold the issued value under its new key") } // A person accepts another value over it: the next ask seals the software's again. if err := inv.AcceptSecretForPair(ctx, "plex-api", "consumer", "kometa", "provider", "", "a-typed-copy"); err != nil { t.Fatal(err) } after, err := inv.KeepIssuedSecret(ctx, "plex-api", "provider", "the-servers-own-token") if err != nil { t.Fatal(err) } if !after.Changed { t.Fatal("an accepted value over the issued one was read as still holding it") } }