Files
mesh-controller/internal/inventory/outside_test.go
T
jochen c9dd07f919
mesh/merge-gate head c9dd07f919a2 merged into trunk 404b4409e70e — pass: builds build-agent, mesh-controller → ace, g14, novox, shanks; no bus step; every …
mesh/repo-check head c9dd07f919a2 merged into trunk 404b4409e70e — pass: its merge-check.sh passed (run 2)
mesh/delivery delivered
Never make a credential only a person can give, and give it once per provider (hq issue 164, ADR 0337)
A Servarr app's API key, an indexer's key and a downloader's password are values the provider's
software holds; the pair path made a random value for every consumer of them, sealed it to both ends
and reported success, and the provider refused it. An offer may now say `"credential": {"issued-by":
"outside"}`: SecretFor never makes a value for it, the plan reads such a pair as given or not given
and sends the consumer no credential file until a person gives it, without refusing its machine;
remakeShared and SharedSecretFor never make a provider's own secret issued outside either.
`secret accept <provider's node> <provider module> <provision>` seals one value to every consumer that
binds the provision from that provider, the provider and the operator, under one generation; the
per-pair form is refused for such a provision, and rotate names the accept. A consumer may wait for a
provision's credential: the controller excuses the wait only when the store bears it out, and its
needs-operator condition names the one accept line.

Adds the replay of issue 164 (TestReplay164), written with only what the controller had before.
2026-10-13 11:37:31 +02:00

196 lines
8.7 KiB
Go

package inventory
import (
"strings"
"testing"
"github.com/novox/mesh-controller/internal/catalogue"
)
// outsideProvision registers a provider offering tv-api with a credential issued outside the mesh (novox/hq ADR
// 0337 §1) and two consumers requiring it, on twoNodesWithKeys' machines.
func outsideProvision(t *testing.T, inv *Inventory) {
t.Helper()
ctx := t.Context()
if err := inv.RegisterModule(ctx, catalogue.Manifest{Module: "tv", Version: "1",
Provides: []catalogue.Offer{{Name: "tv-api", Scope: catalogue.ScopeMesh,
Credential: &catalogue.OfferCredential{IssuedBy: catalogue.IssuedOutside}}}}, Source{}); err != nil {
t.Fatal(err)
}
for _, m := range []string{"requests", "indexer"} {
if err := inv.RegisterModule(ctx, catalogue.Manifest{Module: m, Version: "1", Requires: []string{"tv-api"},
Secrets: map[string]string{"tv-api": "/run/tv-api.secret"}}, Source{}); err != nil {
t.Fatal(err)
}
}
}
// pairRows is how many pair rows the store holds for tv-api.
func pairRows(t *testing.T, inv *Inventory) int {
t.Helper()
var n int
if err := inv.store.Pool().QueryRow(t.Context(), `select count(*) from secret where name = 'tv-api'`).Scan(&n); err != nil {
t.Fatal(err)
}
return n
}
// A credential only a person can give is never made (ADR 0337 §2, issue 164): not for a pair with no row, which is
// what a newly bound consumer has, and not after a key change; a value the mesh made before the rule is not given.
func TestACredentialIssuedOutsideIsNeverMade(t *testing.T) {
inv, ctx := twoNodesWithKeys(t)
outsideProvision(t, inv)
_, err := inv.SecretFor(ctx, "tv-api", "consumer", "requests", "provider", "")
if !IsNotGiven(err) {
t.Fatalf("SecretFor made or refused a credential only a person gives, rather than saying it was not given: %v", err)
}
if !strings.Contains(err.Error(), "secret accept provider tv tv-api") {
t.Fatalf("not given does not name the one line that gives it: %v", err)
}
if n := pairRows(t, inv); n != 0 {
t.Fatalf("the store holds %d rows for tv-api, which nobody gave", n)
}
if _, err := inv.GivenPairSecret(ctx, "tv-api", "consumer", "requests", "provider", "tv", ""); !IsNotGiven(err) {
t.Fatalf("a pair nobody gave read as given: %v", err)
}
if n := pairRows(t, inv); n != 0 {
t.Fatalf("reading the pair wrote %d rows", n)
}
// A value the mesh made before the rule (the minted value of issue 164) is not given.
if _, err := inv.store.Pool().Exec(ctx, `insert into secret (name, consumer, consumer_module, provider,
for_consumer, for_provider, consumer_key, provider_key, local)
select 'tv-api', c.id, 'indexer', p.id, 'x', 'y', c.sealing_key, p.sealing_key, ''
from node c, node p where c.name = 'consumer' and p.name = 'provider'`); err != nil {
t.Fatal(err)
}
if _, err := inv.GivenPairSecret(ctx, "tv-api", "consumer", "indexer", "provider", "tv", ""); !IsNotGiven(err) ||
!strings.Contains(err.Error(), "made by the mesh") {
t.Fatalf("a value the mesh made before the rule read as given: %v", err)
}
// Given, then a key changes: not given, and nothing is made in its place (ADR 0337 §8).
if _, err := inv.AcceptSecretForProvision(ctx, "tv-api", "provider", "tv", "the app's key",
[]PairEnd{{Consumer: "consumer", ConsumerModule: "requests"}}); err != nil {
t.Fatal(err)
}
if _, err := inv.GivenPairSecret(ctx, "tv-api", "consumer", "requests", "provider", "tv", ""); err != nil {
t.Fatalf("the value given does not read as given: %v", err)
}
cn, _ := inv.NodeByName(ctx, "consumer")
key, _ := aSealingKey(t)
if err := inv.RecordSealingKey(ctx, cn.ID, key); err != nil {
t.Fatal(err)
}
if _, err := inv.GivenPairSecret(ctx, "tv-api", "consumer", "requests", "provider", "tv", ""); !IsNotGiven(err) ||
!strings.Contains(err.Error(), "sealing key") {
t.Fatalf("a value given under a key the consumer no longer has read as given: %v", err)
}
var origin string
if err := inv.store.Pool().QueryRow(ctx, `select origin from secret where name = 'tv-api' and consumer_module = 'requests'`).
Scan(&origin); err != nil || origin != OriginAccepted {
t.Fatalf("the given row was replaced (origin %q, %v)", origin, err)
}
// Rotate refuses it, naming the accept (ADR 0337 §9).
err = inv.RotateSecret(ctx, "tv-api", "consumer", "requests", "provider", "")
if err == nil || !strings.Contains(err.Error(), "secret accept provider tv tv-api") {
t.Fatalf("rotate of a credential issued outside was not refused naming the accept: %v", err)
}
}
// One accept for a provider is sealed to every consumer named, to the provider and to the operator, under one
// generation, each row accepted (ADR 0337 §3, issue 165); a consumer with no sealing key is said, not sealed to.
func TestOneAcceptForAProviderIsSealedToEveryConsumer(t *testing.T) {
inv, ctx := twoNodesWithKeys(t)
outsideProvision(t, inv)
operatorKey, operatorOpens := aSealingKey(t)
if _, err := inv.SetOperatorKey(ctx, operatorKey); err != nil {
t.Fatal(err)
}
consumerKey, consumerOpens := aSealingKey(t)
cn, _ := inv.NodeByName(ctx, "consumer")
if err := inv.RecordSealingKey(ctx, cn.ID, consumerKey); err != nil {
t.Fatal(err)
}
if _, err := inv.AddNode(ctx, "keyless"); err != nil {
t.Fatal(err)
}
accepted, err := inv.AcceptSecretForProvision(ctx, "tv-api", "provider", "tv", "the app's key", []PairEnd{
{Consumer: "consumer", ConsumerModule: "requests"}, {Consumer: "consumer", ConsumerModule: "indexer"},
{Consumer: "keyless", ConsumerModule: "requests"}})
if err != nil {
t.Fatal(err)
}
if len(accepted.Sealed) != 2 || len(accepted.Unsealable) != 1 || accepted.Unsealable[0].Consumer != "keyless" {
t.Fatalf("the accept did not seal to both consumers with keys and say the keyless one: %+v", accepted)
}
if strings.Join(accepted.Machines, ",") != "consumer,provider" {
t.Fatalf("the machines to send are the provider's and every consumer's sealed to: %v", accepted.Machines)
}
rows, err := inv.store.Pool().Query(ctx, `select consumer_module, origin, coalesce(generation, ''), for_consumer,
coalesce(operator_sealed, '') from secret where name = 'tv-api'`)
if err != nil {
t.Fatal(err)
}
defer rows.Close()
generations := map[string]bool{}
seen := 0
for rows.Next() {
var module, origin, generation, forConsumer, forOperator string
if err := rows.Scan(&module, &origin, &generation, &forConsumer, &forOperator); err != nil {
t.Fatal(err)
}
seen++
if origin != OriginAccepted || generation == "" {
t.Fatalf("%s's row is not accepted under a generation (origin %q)", module, origin)
}
generations[generation] = true
if opened, ok := consumerOpens(forConsumer); !ok || string(opened) != "the app's key" {
t.Fatalf("%s's copy does not open to the value given", module)
}
if opened, ok := operatorOpens(forOperator); !ok || string(opened) != "the app's key" {
t.Fatalf("%s's operator copy does not open to the value given", module)
}
}
if seen != 2 || len(generations) != 1 {
t.Fatalf("%d rows under %d generations; want 2 under one", seen, len(generations))
}
given, err := inv.GivenPairsOf(ctx, "consumer", "indexer")
if err != nil || !given["tv-api"] {
t.Fatalf("the provision given does not read as given for indexer (%v)", err)
}
// Only an offer that says so is given this way.
if _, err := inv.AcceptSecretForProvision(ctx, "secret", "provider", "gitea", "x", nil); err == nil {
t.Fatal("a provision not issued outside was given once for its provider")
}
}
// The provider's own secret issued outside the mesh, named as a provision's shared credential (ADR 0158, ADR 0228),
// is never made for a consumer that binds: the consumer is told it was not given (ADR 0337 §2, §4).
func TestASharedOwnSecretIssuedOutsideIsNeverMade(t *testing.T) {
inv, ctx := twoNodesWithKeys(t)
if err := inv.RegisterModule(ctx, catalogue.Manifest{Module: "downloader", Version: "1",
OwnSecrets: catalogue.OwnSecrets{"password": {Path: "/run/password", Taken: catalogue.TakenAtStart,
IssuedBy: catalogue.IssuedOutside}},
Provides: []catalogue.Offer{{Name: "downloader-api", Scope: catalogue.ScopeMesh,
Credential: &catalogue.OfferCredential{Own: "password"}}}}, Source{}); err != nil {
t.Fatal(err)
}
_, err := inv.SharedSecretFor(ctx, "downloader-api", "consumer", "gitea", "provider", "downloader", "", "password")
if !IsNotGiven(err) {
t.Fatalf("a shared own secret issued outside was made or refused rather than not given: %v", err)
}
var n int
if err := inv.store.Pool().QueryRow(ctx, `select count(*) from module_secret where module = 'downloader'`).Scan(&n); err != nil || n != 0 {
t.Fatalf("the mesh made %d values of an own secret issued outside (%v)", n, err)
}
if err := inv.remakeShared(ctx, nil, "", "downloader", "password", "", nil, "", "", ""); err == nil ||
!strings.Contains(err.Error(), "issued outside") {
t.Fatalf("remakeShared made a value of an own secret issued outside: %v", err)
}
}