grantsFor granted every consumer a pair credential from the provider was ever made for, so a consumer pinned back to its own store was still asked of the store it left, which then never retired it. A credential whose consumer's resolution binds it elsewhere is now withdrawn like one nobody asks for, kept on record for the login the provider keeps, and said on plan and push.
198 lines
7.7 KiB
Go
198 lines
7.7 KiB
Go
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)
|
|
}
|
|
}
|