mesh/delivery held for a person: merged without a passing check: only a person decides what follows
mesh/delivery-group group fix/1945-credential-when-row-without-seal held: a member is held: novox/mesh-controller@fbf14b68dbab (merged without a passing check:…
mesh/check checking fbf14b68dbab on 40034be479f8
Migration 0108 marks every standing account sealed, ace's stale mesh-build-go rows from 17:19 among them, and the forgetting ran only at a later unassign: the next assignment to ace would have issued nothing again. - The start's look first forgets, from each node's own resolution (assigned and required), the accounts of every module that no longer resolves on its node, and says each; only from a reading that holds assignments, and a node whose resolution is unknown is left alone. - Unassign forgets a module's accounts only once it no longer resolves on the node (a module another requires keeps running), and never the node's own provisioner account; the forgetting moved from the inventory to the act. - Forgetting holds each account's lock across its delete, so an issue in flight never writes a row back after it. - A module whose accounts are not all sealed is granted its sealed accounts alone, and its grant is dropped when none is: an older grant naming ace no longer lets the assigner want it. - An issue's locked section has a deadline of two minutes, since it holds a connection of the store's pool across the vault and the bus. Tests: cases in the existing tables. Research 072: the grants piece (memberships and credentials heard from placement).
196 lines
6.0 KiB
Go
196 lines
6.0 KiB
Go
//go:build integration
|
|
|
|
package inventory
|
|
|
|
import (
|
|
"context"
|
|
"slices"
|
|
"testing"
|
|
"time"
|
|
|
|
"golang.org/x/crypto/bcrypt"
|
|
)
|
|
|
|
func TestABusPasswordIsReturnedOnceAndOnlyItsHashIsKept(t *testing.T) {
|
|
inv := ForTest(t)
|
|
ctx := context.Background()
|
|
|
|
password, err := inv.MintBusPassword(ctx, aBusUser("shop"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if password == "" {
|
|
t.Fatal("no password came back, so nothing can be sealed to the module")
|
|
}
|
|
|
|
hash, known, err := inv.BusUserHash(ctx, "one.shop")
|
|
if err != nil || !known {
|
|
t.Fatalf("the user was not recorded: %v %v", known, err)
|
|
}
|
|
if hash == password {
|
|
t.Fatal("the store holds the password itself")
|
|
}
|
|
if err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)); err != nil {
|
|
t.Fatalf("the recorded hash does not verify the password it was made from: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestMintingAgainRotatesAndMintingIfAbsentHasOneWinner(t *testing.T) {
|
|
inv := ForTest(t)
|
|
ctx := context.Background()
|
|
|
|
first, err := inv.MintBusPassword(ctx, aBusUser("shop"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
second, err := inv.MintBusPassword(ctx, aBusUser("shop"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if first == second {
|
|
t.Fatal("minting twice produced the same password")
|
|
}
|
|
users, err := inv.BusUsers(ctx)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(users) != 1 {
|
|
t.Fatalf("%d users after two mints for one name", len(users))
|
|
}
|
|
hash := users["one.shop"].PasswordHash
|
|
if err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(second)); err != nil {
|
|
t.Fatal("the kept hash is not the newest password's")
|
|
}
|
|
if bcrypt.CompareHashAndPassword([]byte(hash), []byte(first)) == nil {
|
|
t.Fatal("the previous password still verifies, so a rotation revoked nothing")
|
|
}
|
|
|
|
// **An issue's mint is not issued until its seal is marked, and forgotten with its assignment** (ADR 0543 §2, issue
|
|
// 1945, live on 2026-10-25 at 20:04): a row whose credential the node does not hold is not issued.
|
|
if minted, sealed, err := inv.BusAccountIssued(ctx, "one.shop"); err != nil || !minted || !sealed {
|
|
t.Fatalf("a mint that seals in its own act: minted %v sealed %v (%v)", minted, sealed, err)
|
|
}
|
|
if _, err := inv.MintBusPasswordUnsealed(ctx, aBusUser("racer")); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if minted, sealed, err := inv.BusAccountIssued(ctx, "one.racer"); err != nil || !minted || sealed {
|
|
t.Fatalf("an issue's mint before its seal: minted %v sealed %v (%v)", minted, sealed, err)
|
|
}
|
|
if names, err := inv.BusMinted(ctx); err != nil || slices.Contains(names, "one.racer") {
|
|
t.Fatalf("an account not sealed counts as minted: %v (%v)", names, err)
|
|
}
|
|
if err := inv.MarkBusAccountSealed(ctx, "one.racer"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, sealed, _ := inv.BusAccountIssued(ctx, "one.racer"); !sealed {
|
|
t.Fatal("an account marked sealed is not")
|
|
}
|
|
// The lock on an account holds a second issuer until the first lets go.
|
|
release, err := inv.LockBusAccount(ctx, "one.racer")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
waiting := make(chan struct{})
|
|
go func() {
|
|
again, err := inv.LockBusAccount(ctx, "one.racer")
|
|
if err == nil {
|
|
again()
|
|
}
|
|
close(waiting)
|
|
}()
|
|
select {
|
|
case <-waiting:
|
|
t.Fatal("a second issuer took the lock the first held")
|
|
case <-time.After(200 * time.Millisecond):
|
|
}
|
|
release()
|
|
<-waiting
|
|
if _, err := inv.ForgetBusAccountsOf(ctx, "one", "racer", true); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if minted, _, _ := inv.BusAccountIssued(ctx, "one.racer"); minted {
|
|
t.Fatal("an unassigned module's account was kept")
|
|
}
|
|
if minted, _, _ := inv.BusAccountIssued(ctx, "one.shop"); !minted {
|
|
t.Fatal("forgetting one module's accounts forgot another's")
|
|
}
|
|
}
|
|
|
|
func TestForgettingANodeTakesItsBusUsersWithIt(t *testing.T) {
|
|
inv := ForTest(t)
|
|
ctx := context.Background()
|
|
|
|
for _, u := range []BusUser{
|
|
{Username: "node.one", Kind: BusNode, Node: "one"},
|
|
aBusUser("shop"),
|
|
{Username: "node.two", Kind: BusNode, Node: "two"},
|
|
{Username: "controller", Kind: BusController},
|
|
} {
|
|
if _, err := inv.MintBusPassword(ctx, u); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
if err := inv.ForgetBusUsersOf(ctx, "one"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
users, err := inv.BusUsers(ctx)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, still := users["node.one"]; still {
|
|
t.Fatal("a forgotten node's host credential still works")
|
|
}
|
|
if _, still := users["one.shop"]; still {
|
|
t.Fatal("a module on a forgotten node still has a credential")
|
|
}
|
|
// And nothing else went with it: the controller has no node, and another machine's user is
|
|
// another machine's.
|
|
for _, kept := range []string{"node.two", "controller"} {
|
|
if _, ok := users[kept]; !ok {
|
|
t.Fatalf("%s was removed with another node's users", kept)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestForgettingTheUsersOfNoNodeIsRefused(t *testing.T) {
|
|
inv := ForTest(t)
|
|
if err := inv.ForgetBusUsersOf(context.Background(), ""); err == nil {
|
|
t.Fatal("forgetting the bus users of no node was allowed")
|
|
}
|
|
}
|
|
|
|
func TestACredentialTheMeshDidNotMintIsRecordedOnceAndNotOverwritten(t *testing.T) {
|
|
inv := ForTest(t)
|
|
ctx := context.Background()
|
|
|
|
if err := inv.SeedBusUser(ctx, BusUser{Username: "controller", Kind: BusController},
|
|
"bootstrap"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
hash, known, err := inv.BusUserHash(ctx, "controller")
|
|
if err != nil || !known {
|
|
t.Fatalf("the credential was not recorded: %v %v", known, err)
|
|
}
|
|
if err := bcrypt.CompareHashAndPassword([]byte(hash), []byte("bootstrap")); err != nil {
|
|
t.Fatalf("what was recorded does not verify the credential given: %v", err)
|
|
}
|
|
|
|
// Minted since, then seeded again — which is what a restart does. The rotation must stand, or
|
|
// every restart would put the bootstrap password back over it.
|
|
minted, err := inv.MintBusPassword(ctx, BusUser{Username: "controller", Kind: BusController})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := inv.SeedBusUser(ctx, BusUser{Username: "controller", Kind: BusController},
|
|
"bootstrap"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
hash, _, err = inv.BusUserHash(ctx, "controller")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(minted)); err != nil {
|
|
t.Fatal("a restart put the bootstrap credential back over a rotated one")
|
|
}
|
|
}
|