Review of 083: finishing an enrolment whose token was spent takes proof of the key's private half, a live lease and a first delivery — a public key alone cannot replay a spent token; shutdown leaves held messages for the broker; identical builds supersede; what is held leaves room in the prefetch

This commit is contained in:
2026-09-22 14:33:13 +02:00
parent a3b7e830c8
commit 4567fa666c
7 changed files with 214 additions and 25 deletions
+24 -11
View File
@@ -397,14 +397,14 @@ func TestATokenIsClaimedByOnePresenterAndSpentOnlyByIt(t *testing.T) {
if err != nil {
t.Fatal(err)
}
node, err := inv.Claim(ctx, issued.Secret, "key-a")
node, err := inv.Claim(ctx, issued.Secret, "key-a", false)
if err != nil || node.Name != "laptop" {
t.Fatalf("a fresh token was not claimed for its node: %v %q", err, node.Name)
}
if _, err := inv.Claim(ctx, issued.Secret, "key-a"); err != nil {
if _, err := inv.Claim(ctx, issued.Secret, "key-a", false); err != nil {
t.Fatalf("the presenter holding the claim could not claim again after an interruption: %v", err)
}
if _, err := inv.Claim(ctx, issued.Secret, "key-b"); !errors.Is(err, ErrTokenInUse) {
if _, err := inv.Claim(ctx, issued.Secret, "key-b", false); !errors.Is(err, ErrTokenInUse) {
t.Fatalf("a second presenter was not held off while the claim is live: %v", err)
}
if err := inv.Spend(ctx, issued.Secret, "key-b"); !errors.Is(err, ErrTokenRefused) {
@@ -414,18 +414,31 @@ func TestATokenIsClaimedByOnePresenterAndSpentOnlyByIt(t *testing.T) {
t.Fatalf("the presenter holding the claim could not spend it: %v", err)
}
// Spent: nobody else may claim it, ever.
if _, err := inv.Claim(ctx, issued.Secret, "key-b"); !errors.Is(err, ErrTokenRefused) {
if _, err := inv.Claim(ctx, issued.Secret, "key-b", false); !errors.Is(err, ErrTokenRefused) {
t.Fatalf("a spent token was claimed by another presenter: %v", err)
}
// But the presenter that spent it may, and spend it again: its spend reached the store and the
// answer did not reach the node, which asked again — refusing it would lock out a machine the
// mesh holds as enrolled.
if _, err := inv.Claim(ctx, issued.Secret, "key-a"); err != nil {
t.Fatalf("the presenter whose answer was lost after its spend was refused: %v", err)
// Nor the presenter that spent it, without proof it holds the key: a public key is no secret.
if _, err := inv.Claim(ctx, issued.Secret, "key-a", false); !errors.Is(err, ErrTokenRefused) {
t.Fatalf("a spent token was claimed again with no proof of the key: %v", err)
}
// With it, and inside the lease, it may, and spend it again: its spend reached the store and
// the answer did not reach the node, which asked again — refusing it would lock out a machine
// the mesh holds as enrolled.
if _, err := inv.Claim(ctx, issued.Secret, "key-a", true); err != nil {
t.Fatalf("the proven presenter whose answer was lost after its spend was refused: %v", err)
}
if err := inv.Spend(ctx, issued.Secret, "key-a"); err != nil {
t.Fatalf("spending again by the same presenter failed: %v", err)
}
// And not once the lease is over: then a spent token is spent to everyone, proof or not.
if _, err := inv.store.Pool().Exec(ctx,
`update enrolment_token set claimed_until = now() - interval '1 second' where secret = $1`,
hashSecret(issued.Secret)); err != nil {
t.Fatal(err)
}
if _, err := inv.Claim(ctx, issued.Secret, "key-a", true); !errors.Is(err, ErrTokenRefused) {
t.Fatalf("a spent token was claimed again after its lease: %v", err)
}
}
// A claim lapses: a host that gave up and was started over, with keys of its own, is not held off
@@ -440,7 +453,7 @@ func TestAClaimThatLapsedCanBeTakenByAnotherPresenter(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if _, err := inv.Claim(ctx, issued.Secret, "key-a"); err != nil {
if _, err := inv.Claim(ctx, issued.Secret, "key-a", false); err != nil {
t.Fatal(err)
}
if _, err := inv.store.Pool().Exec(ctx,
@@ -448,7 +461,7 @@ func TestAClaimThatLapsedCanBeTakenByAnotherPresenter(t *testing.T) {
hashSecret(issued.Secret)); err != nil {
t.Fatal(err)
}
if _, err := inv.Claim(ctx, issued.Secret, "key-b"); err != nil {
if _, err := inv.Claim(ctx, issued.Secret, "key-b", false); err != nil {
t.Fatalf("a lapsed claim held off a new presenter: %v", err)
}
if err := inv.Spend(ctx, issued.Secret, "key-a"); !errors.Is(err, ErrTokenRefused) {
+10 -7
View File
@@ -216,17 +216,20 @@ var ErrTokenInUse = errors.New("the token is being used by another enrolment")
// of a lease, and says which node it enrols. The same presenter may claim it again, as may anyone
// once the lease has lapsed; nothing is spent until Spend (novox/hq 04-ISSUES/083).
//
// A token this same presenter already spent is claimed again too: its spend reached the store and
// the answer did not reach the node, which asked again. Refusing it then would lock out a machine
// the mesh holds as enrolled — with the key it is still presenting.
func (i *Inventory) Claim(ctx context.Context, secret, by string) (Node, error) {
// A token this same presenter already spent may be claimed again when `again` says so — the
// caller has proof the presenter holds the key's private half — and only while its claim's lease
// is live: its spend reached the store and the answer did not reach the node, which asked again.
// Refusing it then would lock out a machine the mesh holds as enrolled. Without the proof a spent
// token stays spent to everyone, as ADR 0004 says.
func (i *Inventory) Claim(ctx context.Context, secret, by string, again bool) (Node, error) {
var id string
err := i.store.Pool().QueryRow(ctx,
`update enrolment_token set claimed_by = $2, claimed_until = now() + $3::interval
`update enrolment_token set claimed_by = $2,
claimed_until = case when redeemed is null then now() + $3::interval else claimed_until end
where secret = $1 and expires > now()
and ((redeemed is null and (claimed_by is null or claimed_by = $2 or claimed_until < now()))
or (redeemed is not null and claimed_by = $2))
returning node`, hashSecret(secret), by, ClaimLease.String()).Scan(&id)
or (redeemed is not null and $4 and claimed_by = $2 and claimed_until > now()))
returning node`, hashSecret(secret), by, ClaimLease.String(), again).Scan(&id)
if errors.Is(err, pgx.ErrNoRows) {
// Unusable, or held by someone else — told apart, because the second passes.
var held bool