The bus account has JetStream, and the control plane's client has its own inbox
Two refusals the first live connections met. A user in the MESH account was told "JetStream not enabled for account" the first time it bound a consumer: with accounts defined, JetStream is enabled per account, not only globally — the account's setting, which the mesh owns, not the server's block, which it does not. And the control plane's client used a random inbox prefix where it is granted exactly _INBOX.<its user>.>, so the server's first answer could not reach it. The prefix now follows from the user in the URL, for every principal that dials so.
This commit is contained in:
@@ -42,6 +42,12 @@ func Dial(url string, opts ...nats.Option) (*JetStream, error) {
|
|||||||
}
|
}
|
||||||
opts = append(opts, nats.Secure(pinned))
|
opts = append(opts, nats.Secure(pinned))
|
||||||
}
|
}
|
||||||
|
// **Its own inbox, and nothing wider.** Every principal is granted `_INBOX.<its user>.>` and
|
||||||
|
// no other inbox; the client's default prefix is random, and the server refused the first
|
||||||
|
// subscription to it (2026-09-28). The user is in the URL, so the prefix follows from it.
|
||||||
|
if user, _, _ := CredentialIn(url); user != "" {
|
||||||
|
opts = append(opts, nats.CustomInboxPrefix("_INBOX."+user))
|
||||||
|
}
|
||||||
// The address in an error is the address alone. The URL carries this controller's password,
|
// The address in an error is the address alone. The URL carries this controller's password,
|
||||||
// and an error here is written on the assumption it will be logged.
|
// and an error here is written on the assumption it will be logged.
|
||||||
where := BareAddress(url)
|
where := BareAddress(url)
|
||||||
|
|||||||
@@ -521,7 +521,10 @@ func ComposeAccounts(principals []Principal) (string, error) {
|
|||||||
// One account for the mesh: accounts in NATS isolate subject spaces entirely, and the mesh is
|
// One account for the mesh: accounts in NATS isolate subject spaces entirely, and the mesh is
|
||||||
// one space (design 25 §4). The cost of that — that permissions are the only isolation — is
|
// one space (design 25 §4). The cost of that — that permissions are the only isolation — is
|
||||||
// paid in the scoping of every inbox and every ack subject.
|
// paid in the scoping of every inbox and every ack subject.
|
||||||
b.WriteString("accounts {\n MESH {\n users = [\n")
|
// JetStream is enabled per account once accounts exist at all: with only the global block set,
|
||||||
|
// a user in MESH is told "JetStream not enabled for account" the first time it binds a
|
||||||
|
// consumer, which is the first thing every host does (2026-09-28).
|
||||||
|
b.WriteString("accounts {\n MESH {\n jetstream: enabled\n users = [\n")
|
||||||
for _, p := range sorted {
|
for _, p := range sorted {
|
||||||
perms, err := PermissionsFor(p)
|
perms, err := PermissionsFor(p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
+1
@@ -21,6 +21,7 @@ jetstream {
|
|||||||
|
|
||||||
accounts {
|
accounts {
|
||||||
MESH {
|
MESH {
|
||||||
|
jetstream: enabled
|
||||||
users = [
|
users = [
|
||||||
{ user: "controller", password: "$2a$11$cccccccccccccccccccccc", permissions: {
|
{ user: "controller", password: "$2a$11$cccccccccccccccccccccc", permissions: {
|
||||||
publish: { allow: ["$JS.ACK.CONTROL.controller.>", "$JS.ACK.EVENTS.controller.>", "$JS.API.>", "_INBOX.enrol.>", "mesh.control.>", "mesh.node.>", "mesh.seat.mesh-build-machine.accept.>"] }
|
publish: { allow: ["$JS.ACK.CONTROL.controller.>", "$JS.ACK.EVENTS.controller.>", "$JS.API.>", "_INBOX.enrol.>", "mesh.control.>", "mesh.node.>", "mesh.seat.mesh-build-machine.accept.>"] }
|
||||||
|
|||||||
@@ -186,7 +186,13 @@ func TestWhatTheMeshWritesIsUsersAndNothingAboutTheServer(t *testing.T) {
|
|||||||
}
|
}
|
||||||
// None of the server's own settings. Each of these in the mesh's file is a value the controller
|
// None of the server's own settings. Each of these in the mesh's file is a value the controller
|
||||||
// would then own, and the module could no longer change its own image without the mesh agreeing.
|
// would then own, and the module could no longer change its own image without the mesh agreeing.
|
||||||
for _, absent := range []string{"port:", "http:", "jetstream", "tls {", "store_dir", "cert_file"} {
|
// `jetstream {` is the server's block (its store, its limits); `jetstream: enabled` inside the
|
||||||
|
// account is the account's, and the mesh owns the account — a user in it is told "JetStream
|
||||||
|
// not enabled for account" without it (2026-09-28).
|
||||||
|
if !strings.Contains(got, "jetstream: enabled") {
|
||||||
|
t.Errorf("the account does not enable JetStream, so no user in it can bind a consumer")
|
||||||
|
}
|
||||||
|
for _, absent := range []string{"port:", "http:", "jetstream {", "tls {", "store_dir", "cert_file"} {
|
||||||
if strings.Contains(got, absent) {
|
if strings.Contains(got, absent) {
|
||||||
t.Errorf("the accounts file contains %q, which belongs to the module that raises the "+
|
t.Errorf("the accounts file contains %q, which belongs to the module that raises the "+
|
||||||
"server, not to the mesh", absent)
|
"server, not to the mesh", absent)
|
||||||
|
|||||||
Reference in New Issue
Block a user