package main import ( "strings" "testing" "github.com/novox/mesh-controller/internal/catalogue" "github.com/novox/mesh-controller/internal/inventory" ) // novox/hq issue 274: a provider is granted exactly the consumers whose own resolution binds them to // it — not every consumer a pair credential from it was ever made for. // grantOf is the grant of one provision to one consuming module, and whether there is one at all. func grantOf(grants []catalogue.Grant, provision, consumer, module string) (catalogue.Grant, bool) { for _, g := range grants { if g.Provision == provision && g.Consumer == consumer && (g.From == module || g.From == "") { return g, true } } return catalogue.Grant{}, false } // The morning after issue 273, through the stores: a consumer was bound to the store on another // machine, a credential from there was made, and a person pinned it back to the store beside it. Both // credentials are on record. The store it left is no longer granted it — so it retires it and keeps // its data (ADR 0230) — and says so; the store it is bound to keeps its grant; and the credential from // the store it left stays on record. func TestAConsumerPinnedBackIsNoLongerGrantedByTheProviderItLeft(t *testing.T) { open := aMesh(t) ctx := t.Context() inv := open.inventory for _, m := range storeManifests() { register(t, open, m) } if _, err := inv.SeedSeats(ctx, catalogue.DefaultSeats()); err != nil { t.Fatal(err) } if _, err := assign(ctx, open, "anchor", "store"); err != nil { t.Fatal(err) } if err := inv.HoldSeat(ctx, "mesh-store", catalogue.ScopeMesh, "anchor", "store"); err != nil { t.Fatal(err) } for _, a := range [][2]string{{"laptop", "store"}, {"laptop", "board"}} { if _, err := assign(ctx, open, a[0], a[1]); err != nil { t.Fatalf("assign %s %s: %v", a[0], a[1], err) } } // Bound to the anchor's store, and sent so: the credential from the anchor is made and recorded. if err := inv.PinProvision(ctx, "laptop", "postgres-database", "anchor", "store"); err != nil { t.Fatal(err) } plan, _, err := planFor(ctx, open, "laptop") if err != nil { t.Fatal(err) } if n := need(t, plan, "board", "postgres-database"); n.From != "anchor" { t.Fatalf("pinned to the anchor and bound to %s", n.From) } if err := inv.RecordBindings(ctx, "laptop", boundToData(plan, nil)); err != nil { t.Fatal(err) } grants, _, unbound, err := grantsFor(ctx, open, "anchor") if err != nil { t.Fatal(err) } if g, ok := grantOf(grants, "postgres-database", "laptop", "board"); !ok || g.From != "board" || len(unbound) != 0 { t.Fatalf("a consumer bound to the anchor is not granted there: %+v, unbound %+v", grants, unbound) } // Pinned back beside its data, as the operator did. if err := inv.PinProvision(ctx, "laptop", "postgres-database", "laptop", "store"); err != nil { t.Fatal(err) } plan, _, err = planFor(ctx, open, "laptop") if err != nil { t.Fatal(err) } if n := need(t, plan, "board", "postgres-database"); n.From != "laptop" { t.Fatalf("pinned back to the laptop and bound to %s", n.From) } if err := inv.RecordBindings(ctx, "laptop", boundToData(plan, nil)); err != nil { t.Fatal(err) } holders, err := inv.HoldersOf(ctx, "postgres-database", "laptop") if err != nil { t.Fatal(err) } if len(holders) != 2 { t.Fatalf("today's state is two credentials on record, one from each store: %+v", holders) } // The store it left: no longer granted, and said. grants, _, unbound, err = grantsFor(ctx, open, "anchor") if err != nil { t.Fatal(err) } if g, ok := grantOf(grants, "postgres-database", "laptop", "board"); ok && g.From != "" { t.Fatalf("the anchor's store is still granted a consumer bound to the laptop's: %+v", g) } if len(unbound) != 1 || unbound[0].Module != "board" || unbound[0].Provider != "anchor" || strings.Join(unbound[0].BoundTo, ",") != "laptop" { t.Fatalf("the consumer that moved is not the one said: %+v", unbound) } planned, settings, err := planFor(ctx, open, "anchor") if err != nil { t.Fatal(err) } declared, err := declarationFor(ctx, open, "anchor", planned, settings) if err != nil { t.Fatal(err) } if got := declared.Received["store"]["postgres-database"]; len(got) != 0 { t.Fatalf("the anchor's store is still told about %+v", got) } said := printed(t, func() error { reportLeftOut("anchor", declared); return nil }) if !strings.Contains(said, "board on laptop is bound to laptop for postgres-database, not to anchor") || !strings.Contains(said, "cleanup delete") { t.Fatalf("the push does not say whom the anchor no longer grants:\n%s", said) } // The store it is bound to: granted. grants, _, unbound, err = grantsFor(ctx, open, "laptop") if err != nil { t.Fatal(err) } if g, ok := grantOf(grants, "postgres-database", "laptop", "board"); !ok || g.From != "board" || len(unbound) != 0 { t.Fatalf("the consumer is not granted by the store it is bound to: %+v, unbound %+v", grants, unbound) } // And the credential from the store it left is kept: the key to the login and data held there. if holders, err = inv.HoldersOf(ctx, "postgres-database", "laptop"); err != nil || len(holders) != 2 { t.Fatalf("a credential was forgotten while its provider still holds the login: %+v, %v", holders, err) } } // A consumer whose resolution cannot be read is an error, never a consumer bound nowhere — withdrawing // a grant on that reading would take its access away (issue 152). func TestAConsumerWhoseResolutionCannotBeReadIsNotWithdrawn(t *testing.T) { open := aMesh(t) ctx := t.Context() inv := open.inventory for _, m := range storeManifests() { register(t, open, m) } for _, a := range [][2]string{{"anchor", "store"}, {"laptop", "board"}} { if _, err := assign(ctx, open, a[0], a[1]); err != nil { t.Fatalf("assign %s %s: %v", a[0], a[1], err) } } if _, _, err := planFor(ctx, open, "laptop"); err != nil { t.Fatal(err) } // The laptop's key changes to one nothing can seal to: its resolution cannot be completed, and // that is not its set failing to compose. laptop, err := inv.NodeByName(ctx, "laptop") if err != nil { t.Fatal(err) } if err := inv.RecordSealingKey(ctx, laptop.ID, "not a key"); err != nil { t.Fatal(err) } if _, _, err := planFor(ctx, open, "laptop"); err == nil || unresolvable(err) { t.Fatalf("the seam this test relies on moved: %v", err) } grants, _, unbound, err := grantsFor(ctx, open, "anchor") if err == nil { t.Fatalf("an unreadable consumer was answered: grants %+v, unbound %+v", grants, unbound) } if !strings.Contains(err.Error(), "what laptop asked of postgres-database cannot be read") { t.Fatalf("the error does not say whose resolution could not be read: %v", err) } } // The rule itself, on a resolution: bound is the provider a need for exactly that credential is // answered by, never one answered by a record, and a different local name is a different credential. func TestBindsFromIsTheProviderOfThatCredential(t *testing.T) { r := catalogue.Resolution{Needs: []catalogue.Needed{ {Name: "postgres-database", For: "board", From: "laptop"}, {Name: "postgres-database", For: "board", From: "laptop", Local: "reports"}, {Name: "postgres-database", For: "wiki", From: "anchor"}, {Name: "a-licence", For: "board", From: "the-licence", ByRecord: true}, }} s := inventory.Secret{Name: "postgres-database", ConsumerModule: "board"} if got := r.BindsFrom(s.Name, s.ConsumerModule, s.Local); strings.Join(got, ",") != "laptop" { t.Fatalf("board's credential is bound to %v", got) } if got := r.BindsFrom("postgres-database", "board", "archive"); len(got) != 0 { t.Fatalf("a local name nothing asks for is bound to %v", got) } if got := r.BindsFrom("a-licence", "board", ""); len(got) != 0 { t.Fatalf("a need answered by a record is bound to %v", got) } }