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) } }