From 6005a8471fb42130e8036d8508eed80ed4d4c6e8 Mon Sep 17 00:00:00 2001 From: jochen Date: Mon, 28 Sep 2026 01:46:16 +0200 Subject: [PATCH] A principal may hear what its consumer delivers A push consumer delivers on _DELIVER., and a client bound to it subscribes exactly that. No principal was granted it, and the server refused every one the first time it bound a consumer: the control plane, each machine, and a module would have been next. Each kind is granted its own consumers' delivery subjects and no other's. The line announcing the raised bus printed the URL with the credential in it; the address alone now. --- cmd/mesh-controller/push.go | 2 +- internal/broker/nats.go | 13 +++++++++++-- internal/broker/testdata/composed.conf | 10 +++++----- 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/cmd/mesh-controller/push.go b/cmd/mesh-controller/push.go index 9553f3c..4e14355 100644 --- a/cmd/mesh-controller/push.go +++ b/cmd/mesh-controller/push.go @@ -765,6 +765,6 @@ func raiseTheBus(ctx context.Context, inv *inventory.Inventory, address string) return err } fmt.Printf("the bus at %s has its streams, and %d machine(s) can hear a declaration\n", - address, len(names)) + broker.BareAddress(address), len(names)) return nil } diff --git a/internal/broker/nats.go b/internal/broker/nats.go index 562c376..6ae882d 100644 --- a/internal/broker/nats.go +++ b/internal/broker/nats.go @@ -169,7 +169,11 @@ func PermissionsFor(p Principal) (Permissions, error) { // The controller owns the mesh's own traffic and the streams. It is the only writer of // stream definitions (design 25 ยง3), so it alone reaches the JetStream API. pub = []string{"mesh.control.>", "mesh.node.>", "$JS.API.>"} - sub = []string{"mesh.control.>", "$JS.API.>"} + // **And where its consumers deliver.** A push consumer delivers on `_DELIVER.`, + // and a client bound to it subscribes exactly that; the server refused it for every + // principal the first time one bound a consumer (2026-09-28). Each kind below is granted + // its own consumers' delivery subjects and no other's. + sub = []string{"mesh.control.>", "$JS.API.>", "_DELIVER." + ControllerName, "_DELIVER." + ControllerName + ".>"} // Work the mesh's own flows submit to a role, and the outcomes they wait on (ADR 0121). A // build is the one today: the controller asks, and reads the answer from the seat's event @@ -252,7 +256,7 @@ func PermissionsFor(p Principal) (Permissions, error) { "mesh.control." + p.Node + ".>", "$JS.API.CONSUMER.INFO.NODES." + p.Node, } - sub = []string{"mesh.node." + p.Node + ".declare"} + sub = []string{"mesh.node." + p.Node + ".declare", "_DELIVER." + p.Node} case KindModule: // 1. Its own namespace: it publishes its events there and serves its tools there. Nothing @@ -285,7 +289,12 @@ func PermissionsFor(p Principal) (Permissions, error) { } // 3. Seats it holds: full participation. + // Its consumer's name, not ConsumerFor: that asks for these permissions to build the + // consumer, and would ask forever. A subject for a consumer that turns out not to exist + // grants nothing anybody can use. + sub = append(sub, "_DELIVER."+consumerDurable(p)) for _, s := range p.Holds { + sub = append(sub, "_DELIVER.SEAT_"+upperSnake(s.Name)+"_worker") for _, a := range s.Accepts { sub = append(sub, seatSubject(s, "accept", a)) } diff --git a/internal/broker/testdata/composed.conf b/internal/broker/testdata/composed.conf index 6e6eb35..9ebd7e8 100644 --- a/internal/broker/testdata/composed.conf +++ b/internal/broker/testdata/composed.conf @@ -25,7 +25,7 @@ accounts { users = [ { 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.>"] } - subscribe: { allow: ["$JS.API.>", "_INBOX.controller.>", "mesh.control.>", "mesh.mod.mesh-catalog.event.catching-up", "mesh.mod.mesh-catalog.event.upgraded", "mesh.seat.mesh-build-machine.event.built"] } + subscribe: { allow: ["$JS.API.>", "_DELIVER.controller", "_DELIVER.controller.>", "_INBOX.controller.>", "mesh.control.>", "mesh.mod.mesh-catalog.event.catching-up", "mesh.mod.mesh-catalog.event.upgraded", "mesh.seat.mesh-build-machine.event.built"] } allow_responses: { max: 1, ttl: "1m" } } } { user: "enrol.one", password: "$2a$11$eeeeeeeeeeeeeeeeeeeeee", permissions: { @@ -34,20 +34,20 @@ accounts { } } { user: "node.one", password: "$2a$11$nnnnnnnnnnnnnnnnnnnnnn", permissions: { publish: { allow: ["$JS.ACK.NODES.one.>", "$JS.API.CONSUMER.INFO.NODES.one", "mesh.control.one.>"] } - subscribe: { allow: ["_INBOX.node.one.>", "mesh.node.one.declare"] } + subscribe: { allow: ["_DELIVER.one", "_INBOX.node.one.>", "mesh.node.one.declare"] } } } { user: "one.telegram", password: "$2a$11$tttttttttttttttttttttt", permissions: { publish: { allow: ["$JS.ACK.EVENTS.one_telegram.>", "mesh.seat.telegram-sender.event.delivered", "mesh.seat.telegram-sender.event.failed"] } - subscribe: { allow: ["_INBOX.one.telegram.>", "mesh.mod.telegram.tool.status", "mesh.seat.telegram-sender.accept.send"] } + subscribe: { allow: ["_DELIVER.SEAT_TELEGRAM_SENDER_worker", "_DELIVER.one_telegram", "_INBOX.one.telegram.>", "mesh.mod.telegram.tool.status", "mesh.seat.telegram-sender.accept.send"] } allow_responses: { max: 1, ttl: "1m" } } } { user: "two.audit", password: "$2a$11$aaaaaaaaaaaaaaaaaaaaaa", permissions: { publish: { allow: ["$JS.ACK.EVENTS.two_audit.>"] } - subscribe: { allow: ["_INBOX.two.audit.>", "mesh.mod.shop.event.order.placed"] } + subscribe: { allow: ["_DELIVER.two_audit", "_INBOX.two.audit.>", "mesh.mod.shop.event.order.placed"] } } } { user: "two.shop", password: "$2a$11$ssssssssssssssssssssss", permissions: { publish: { allow: ["$JS.ACK.EVENTS.two_shop.>", "mesh.mod.shop.event.order.placed", "mesh.seat.telegram-sender.accept.send"] } - subscribe: { allow: ["_INBOX.two.shop.>"] } + subscribe: { allow: ["_DELIVER.two_shop", "_INBOX.two.shop.>"] } } } ] }