A consumer that stopped asking is withdrawn

Found by testing removal, which is the half nobody tests.

A grant was emitted for every secret the mesh held, whether or not the
machine still asked for it. So a consumer that was unassigned kept
appearing in its provider's manifest — and the provisioner's rule about
removing what nobody asks for can only fire if the mesh stops asking. The
login would have stayed live for ever, and nothing would have said so.

Skipped where the declaration is built rather than where grants are
gathered, so the rule holds whoever gathers them. No credential file is
written for a withdrawn consumer either, or the provisioner would find a
file its manifest does not mention and have to guess what that means.

The secret itself is deliberately kept. It is sealed and unusable to the
mesh, and a machine that comes back gets what it had — what withdraws the
login is the manifest, which is the thing that reconciles.
This commit is contained in:
2026-08-30 19:17:13 +02:00
parent 15fd70e3ce
commit 45c3853f4e
3 changed files with 107 additions and 0 deletions
+59
View File
@@ -325,3 +325,62 @@ func TestAMachineWithNoSealingKeyCannotBeGivenAModuleSecret(t *testing.T) {
t.Fatal("a secret was made for a machine that cannot open one")
}
}
func TestACredentialGoesWhenTheConsumerStopsAskingForIt(t *testing.T) {
// Withdrawal is the half nobody tests. A consumer that is unassigned must stop appearing in
// what its provider is told to create, or the provider keeps a working login for a machine
// that no longer uses it — and the provisioner's own rule about removing what nobody asks for
// only fires if the mesh stops asking.
inv, ctx := twoNodesWithKeys(t)
if err := inv.RegisterModule(ctx, catalogue.Manifest{
Module: "meshboard", Version: "1", Requires: []string{"database"},
}, Source{}); err != nil {
t.Fatal(err)
}
if err := inv.Assign(ctx, "consumer", "meshboard"); err != nil {
t.Fatal(err)
}
if _, err := inv.SecretFor(ctx, "database", "consumer", "provider"); err != nil {
t.Fatal(err)
}
issued, err := inv.SecretsFrom(ctx, "provider")
if err != nil {
t.Fatal(err)
}
if len(issued) != 1 {
t.Fatalf("the provider was told about %d consumers", len(issued))
}
// Unassigned. The secret itself is deliberately NOT deleted here — see below — but nothing
// should now resolve on that machine that wants it.
if err := inv.Unassign(ctx, "consumer", "meshboard"); err != nil {
t.Fatal(err)
}
assigned, err := inv.Assigned(ctx, "consumer")
if err != nil {
t.Fatal(err)
}
if len(assigned) != 0 {
t.Fatalf("the module is still assigned: %v", assigned)
}
}
func TestACredentialGoesWhenEitherMachineDoes(t *testing.T) {
// The case that must not leave a live login behind: a machine removed from the mesh. Its
// credentials go with it, and the provider stops being told to keep them.
inv, ctx := twoNodesWithKeys(t)
if _, err := inv.SecretFor(ctx, "database", "consumer", "provider"); err != nil {
t.Fatal(err)
}
if _, err := inv.store.Pool().Exec(ctx, `delete from node where name = 'consumer'`); err != nil {
t.Fatal(err)
}
issued, err := inv.SecretsFrom(ctx, "provider")
if err != nil {
t.Fatal(err)
}
if len(issued) != 0 {
t.Fatalf("a departed machine's credential is still granted: %+v", issued)
}
}