From 61b67d61e2aaed1457e6e58410a8e0597805b4c0 Mon Sep 17 00:00:00 2001 From: jochen Date: Fri, 30 Oct 2026 18:19:20 +0100 Subject: [PATCH] Export the accounts block's user entry, so a kept user is written in ComposeAccounts' own lines (issue 774) mesh-grants' refused.go keptEntry wrote a user of the accounts block from grants it keeps by copying ComposeAccounts' lines; a change to one would leave the other writing a list the bus reads differently. buscompose.AccountsEntry(user, passwordHash, perms) is now the one writer, and ComposeAccounts uses it. No change to what is written. --- go/buscompose/buscompose_test.go | 7 +++++++ go/buscompose/nats.go | 31 ++++++++++++++++++++----------- 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/go/buscompose/buscompose_test.go b/go/buscompose/buscompose_test.go index 3d02084..d483eba 100644 --- a/go/buscompose/buscompose_test.go +++ b/go/buscompose/buscompose_test.go @@ -573,6 +573,13 @@ func TestTheCopiedComposerComposesAsTheControllers(t *testing.T) { if reads[VaultSeat] != ControllerLease+","+GrantsAccounts || reads["mesh-config"] != "" || reads["restic"] != "" || reads["messenger"] != "" { t.Errorf("the reads granted for their readers: %v", reads) } + // AccountsEntry is the very lines ComposeAccounts writes for a user, so a writer of kept users repeats none. + filled, _ := WithPasswords(judged, map[string]string{judged[0].Username(): "$2a$11$h"}) + list, err := ComposeAccounts(filled[:1]) + perms, perr := PermissionsFor(filled[0]) + if err != nil || perr != nil || !strings.Contains(list, " users = [\n"+AccountsEntry(filled[0].Username(), "$2a$11$h", perms)+" ]\n") { + t.Errorf("the accounts entry is not ComposeAccounts' own: %v %v\n%s", err, perr, list) + } c, _ := ConsumerFor(Principal{Kind: KindModule, Node: "a", Module: "m", Consumes: []string{"x.happened"}}) cfg := ConsumerConfig(c) if cfg.Durable != "a_m" || cfg.AckPolicy != jetstream.AckExplicitPolicy || cfg.AckWait != 30*time.Second || diff --git a/go/buscompose/nats.go b/go/buscompose/nats.go index 7e536e2..85dbab2 100644 --- a/go/buscompose/nats.go +++ b/go/buscompose/nats.go @@ -1680,17 +1680,7 @@ func ComposeAccounts(principals []Principal) (string, error) { if p.PasswordHash == "" { return "", fmt.Errorf("%s has no password hash: a user without one is a user anybody is", p.Username()) } - fmt.Fprintf(&b, " { user: %q, password: %q, permissions: {\n", p.Username(), p.PasswordHash) - if len(perms.PublishDeny) > 0 { - fmt.Fprintf(&b, " publish: { allow: [%s], deny: [%s] }\n", quoted(perms.Publish), quoted(perms.PublishDeny)) - } else { - fmt.Fprintf(&b, " publish: { allow: [%s] }\n", quoted(perms.Publish)) - } - fmt.Fprintf(&b, " subscribe: { allow: [%s] }\n", quoted(perms.Subscribe)) - if perms.AllowResponses { - fmt.Fprintf(&b, " allow_responses: { max: 1, ttl: \"%dm\" }\n", int(ResponseTTL/time.Minute)) - } - b.WriteString(" } }\n") + b.WriteString(AccountsEntry(p.Username(), p.PasswordHash, perms)) } b.WriteString(" ]\n }\n") b.WriteString(systemAccount) @@ -1698,6 +1688,25 @@ func ComposeAccounts(principals []Principal) (string, error) { return b.String(), nil } +// AccountsEntry is one user of the accounts block, as ComposeAccounts writes each: its name, its password hash and its +// permissions, in the lines the bus reads. Exported so a writer that keeps a user from grants it holds rather than +// composes (mesh-grants' refused users) writes the same lines, never a copy of them (issue 774). +func AccountsEntry(user, passwordHash string, perms Permissions) string { + var b strings.Builder + fmt.Fprintf(&b, " { user: %q, password: %q, permissions: {\n", user, passwordHash) + if len(perms.PublishDeny) > 0 { + fmt.Fprintf(&b, " publish: { allow: [%s], deny: [%s] }\n", quoted(perms.Publish), quoted(perms.PublishDeny)) + } else { + fmt.Fprintf(&b, " publish: { allow: [%s] }\n", quoted(perms.Publish)) + } + fmt.Fprintf(&b, " subscribe: { allow: [%s] }\n", quoted(perms.Subscribe)) + if perms.AllowResponses { + fmt.Fprintf(&b, " allow_responses: { max: 1, ttl: \"%dm\" }\n", int(ResponseTTL/time.Minute)) + } + b.WriteString(" } }\n") + return b.String() +} + // WatchmanModule is the one module whose principal hears the bus's connect and disconnect events and asks its list of // connections (novox/hq ADR 0431 ยง2, issue 1147): the watchman of the repository mesh-delivery, which writes presence // from them. No other principal is granted the subjects below, and nothing is a wildcard over them.