Compose where a declaration was sent: secrets, credentials, acceptances, the hub's peers, adoption, assignments and the bus's user list (issue 1945, B2, B6)
Each act that changed what a node runs now writes that node's compositions, and its wants follow by the assigner's rules; a bus upgrade composes the bus's machines and answers the want the operator writes, since the assigner never moves the bus (ADR 0539 clauses 5, 7 and 15). A node-engine may ask its own backup holder before a data module moves.
This commit is contained in:
@@ -112,11 +112,13 @@ func acceptForProvision(ctx context.Context, provider, providerModule, provision
|
||||
if len(accepted.Sealed) == 0 {
|
||||
return nil
|
||||
}
|
||||
if err := sendTo(ctx, open, accepted.Machines); err != nil {
|
||||
return fmt.Errorf("%s is sealed and could not be sent: %w; `push %s` sends it", provision, err,
|
||||
strings.Join(accepted.Machines, "`, `push "))
|
||||
// Composed into each consumer's composition (novox/hq ADR 0539 clause 5): a new revision its want then names.
|
||||
if _, err := composeModules(ctx, open, accepted.Machines); err != nil {
|
||||
return fmt.Errorf("%s is sealed and its compositions on %s could not be written: %w", provision,
|
||||
strings.Join(accepted.Machines, ", "), err)
|
||||
}
|
||||
fmt.Printf(" sent %s; each consumer starts again on it\n", strings.Join(accepted.Machines, ", "))
|
||||
fmt.Printf(" composed for %s; each consumer starts again on it once its want names the new composition\n",
|
||||
strings.Join(accepted.Machines, ", "))
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -171,8 +171,21 @@ func assignWith(ctx context.Context, open *stores, node string, opts assignOptio
|
||||
answer += "\n but " + catalogue.WrongMachine(u.Module, c, node)
|
||||
}
|
||||
}
|
||||
return answer + fmt.Sprintf("\n run `push %s` to send it", node) +
|
||||
blockedElsewhere(ctx, open, node), nil
|
||||
return answer + composedAfterAct(ctx, open, node) + blockedElsewhere(ctx, open, node), nil
|
||||
}
|
||||
|
||||
// composedAfterAct composes the node an assignment or its end changed (novox/hq ADR 0539 clause 5): what the
|
||||
// answer says of it. The assigner then writes the node's wants by its rules; nothing is sent.
|
||||
func composedAfterAct(ctx context.Context, open *stores, node string) string {
|
||||
wrote, err := composeModules(ctx, open, []string{node})
|
||||
if err != nil {
|
||||
return fmt.Sprintf("\n but its compositions on %s could not all be written: %v", node, firstLine(err.Error()))
|
||||
}
|
||||
if len(wrote[node]) == 0 {
|
||||
return fmt.Sprintf("\n %s's compositions were already as composed now", node)
|
||||
}
|
||||
return fmt.Sprintf("\n composed for %s (%d composition(s) written): its wants follow by the assigner's rules", node,
|
||||
len(wrote[node]))
|
||||
}
|
||||
|
||||
// seatDependenciesOnAssign is the refusal ADR 0207 makes at assignment, or nothing, with the
|
||||
@@ -273,8 +286,8 @@ func unassign(ctx context.Context, open *stores, node string, modules ...string)
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
answer := fmt.Sprintf("%s no longer runs %s — run `push %s` to make it so",
|
||||
node, strings.Join(modules, ", "), node)
|
||||
answer := fmt.Sprintf("%s no longer runs %s: its want is written removed by the assigner",
|
||||
node, strings.Join(modules, ", "))
|
||||
after := resolvedAfter(ctx, open, shelf, node)
|
||||
for _, line := range unheldChangeTo(shelf, node, assigned, after.modules, after.unheld) {
|
||||
answer += "\n " + line
|
||||
@@ -295,7 +308,7 @@ func unassign(ctx context.Context, open *stores, node string, modules ...string)
|
||||
for _, line := range keptOnUnassign(ctx, open.inventory, node, modules) {
|
||||
answer += "\n " + line
|
||||
}
|
||||
return answer + blockedElsewhere(ctx, open, node), nil
|
||||
return answer + composedAfterAct(ctx, open, node) + blockedElsewhere(ctx, open, node), nil
|
||||
}
|
||||
|
||||
// afterAct is the one resolution of a node an act's answer is said from, taken after its write
|
||||
|
||||
@@ -205,9 +205,9 @@ func adoptedNodes(nodes []inventory.Node) []string {
|
||||
// The operator's acts on an adopted node (novox/hq ADR 0100). Called by the command line and the
|
||||
// command API alike, so a refusal is the same refusal in the same words at both (ADR 0035).
|
||||
|
||||
// sendNodes sends the named machines what they should be now. A variable so a test can see what
|
||||
// an act would send without a broker.
|
||||
var sendNodes = sendTo
|
||||
// sendNodes composes the named machines as they should be now (novox/hq ADR 0539 clause 5): each node's want then
|
||||
// names what changed. A variable so a test can see what an act would compose without a broker.
|
||||
var sendNodes = composeAndSay
|
||||
|
||||
// DefaultFilter is the module converging a node assigns to load the mesh's derived filter.
|
||||
const DefaultFilter = "nftables"
|
||||
|
||||
@@ -10,8 +10,10 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/nats-io/nats.go"
|
||||
"github.com/nats-io/nats.go/jetstream"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
sdkdelivery "git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
"github.com/novox/mesh-controller/internal/conditions"
|
||||
"github.com/novox/mesh-controller/internal/inventory"
|
||||
"github.com/novox/mesh-controller/internal/link"
|
||||
@@ -229,17 +231,85 @@ func busCommand(ctx context.Context, args []string) error {
|
||||
fmt.Printf("bus upgrade %d: %s %s → %s on %s; streams snapshotted at %s; %s\n", step.ID, b.module, step.From,
|
||||
short(b.to), strings.Join(moving, ", "), where, map[bool]string{true: "reversible: putting the old build back undoes it",
|
||||
false: "NOT reversible: the snapshot is the only way back"}[*reversible])
|
||||
sent, err := sendToEach(withBusStep(ctx), open, moving)
|
||||
// **Nothing is sent, and the assigner never moves the bus** (novox/hq ADR 0539 clause 15): the bus's machines are
|
||||
// composed at the build the mesh holds, and the operator writes each machine's want with the operator's credential,
|
||||
// recorded, naming that composition.
|
||||
wants, err := busWantsToWrite(withBusStep(ctx), open, b.module, moving)
|
||||
if err != nil {
|
||||
_ = inv.EndBusStep(ctx, step.ID, "failed", "the send was refused: "+err.Error())
|
||||
return fmt.Errorf("the bus's machine could not be sent its new build: %w — nothing was replaced", err)
|
||||
_ = inv.EndBusStep(ctx, step.ID, "failed", "the bus's composition could not be written: "+err.Error())
|
||||
return fmt.Errorf("the bus's machines could not be composed at its new build: %w — nothing was replaced", err)
|
||||
}
|
||||
fmt.Printf("composed %s at the bus's new build. Write each want below with the operator's credential, and record it "+
|
||||
"in the hand-act log; `bus-maintenance` is open until the bus answers healthy again — every stream, every durable "+
|
||||
"consumer, a round trip to the machines (H-bus) — within %s, or the step is said failed with its snapshot as the "+
|
||||
"way back. `bus` says how it went\n", strings.Join(moving, ", "), busStepBound)
|
||||
for _, w := range wants {
|
||||
fmt.Println(" " + w)
|
||||
}
|
||||
fmt.Printf("sent %s; `bus-maintenance` is open until the bus answers healthy again — every stream, every durable "+
|
||||
"consumer, a round trip to the machines (H-bus) — within %s, or the step is said failed with its snapshot "+
|
||||
"as the way back. `bus` says how it went\n", strings.Join(sent, ", "), busStepBound)
|
||||
return nil
|
||||
}
|
||||
|
||||
// busWantsToWrite composes the bus's machines and answers, for each, the want the operator writes: its key in the
|
||||
// assigner's bucket and its value, naming the fingerprint the bus module is registered at and that machine's
|
||||
// composition of it.
|
||||
func busWantsToWrite(ctx context.Context, open *stores, module string, machines []string) ([]string, error) {
|
||||
if _, err := composeModules(ctx, open, machines); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fps, err := currentFingerprints(ctx, open.inventory)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bus, err := aBus()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer bus.Close()
|
||||
js, err := jetstream.New(bus.Conn())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
digests, err := js.KeyValue(ctx, sdkdelivery.ControllerComposedBucket)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
digestOf := func(node string) (string, error) {
|
||||
key, err := sdkdelivery.ComposedKey(node, module, fps[module])
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
e, err := digests.Get(ctx, key)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("%s: %w", key, err)
|
||||
}
|
||||
v, err := sdkdelivery.DecodeComposedDigest(e.Value())
|
||||
return v.Digest, err
|
||||
}
|
||||
return busWants(module, fps[module], machines, digestOf)
|
||||
}
|
||||
|
||||
// busWants is each machine's want for the bus module, as the operator writes it: `<bucket> <key> <value>`.
|
||||
func busWants(module, fp string, machines []string, digestOf func(node string) (string, error)) ([]string, error) {
|
||||
var out []string
|
||||
for _, node := range machines {
|
||||
digest, err := digestOf(node)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
key, err := sdkdelivery.WantKey(node, module)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
value, err := sdkdelivery.EncodeWant(sdkdelivery.Want{FP: fp, Composed: digest,
|
||||
Words: "the bus moved by a person's step (bus upgrade, ADR 0539 clause 15)"})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out = append(out, fmt.Sprintf("%s %s %s", sdkdelivery.AssignerWantsBucket, key, value))
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// busStatus is `bus`: what an upgrade would do, and the last step.
|
||||
func busStatus(ctx context.Context) error {
|
||||
open, err := openStores(ctx)
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
sdkdelivery "git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
)
|
||||
|
||||
// 1945/AC7: a bus upgrade answers, for each machine, the want the operator writes: the assigner's bucket, the
|
||||
// machine's key for the bus module, and a want naming the bus's fingerprint and that machine's composition of it. It
|
||||
// sends nothing: nothing it calls publishes on a node's declaration subject (busWants composes no send at all).
|
||||
func TestABusUpgradeAnswersTheWantTheOperatorWrites(t *testing.T) {
|
||||
fp := sdkdelivery.SourcePrefix2 + strings.Repeat("b", 64)
|
||||
digests := map[string]string{"novox": "sha256:" + strings.Repeat("1", 64), "ace": "sha256:" + strings.Repeat("2", 64)}
|
||||
lines, err := busWants("nats", fp, []string{"ace", "novox"}, func(node string) (string, error) { return digests[node], nil })
|
||||
if err != nil || len(lines) != 2 {
|
||||
t.Fatalf("busWants = %v, %v", lines, err)
|
||||
}
|
||||
for i, node := range []string{"ace", "novox"} {
|
||||
parts := strings.SplitN(lines[i], " ", 3)
|
||||
if len(parts) != 3 || parts[0] != sdkdelivery.AssignerWantsBucket || parts[1] != "want."+node+".nats" {
|
||||
t.Fatalf("line %q", lines[i])
|
||||
}
|
||||
w, err := sdkdelivery.DecodeWant([]byte(parts[2]))
|
||||
if err != nil || w.FP != fp || w.Composed != digests[node] {
|
||||
t.Errorf("%s's want = %+v, %v", node, w, err)
|
||||
}
|
||||
}
|
||||
if _, err := busWants("nats", fp, []string{"ace"}, func(string) (string, error) { return "", nil }); err == nil {
|
||||
t.Errorf("a want naming no composition was answered")
|
||||
}
|
||||
}
|
||||
@@ -62,12 +62,14 @@ func replaceGiven(ctx context.Context, open *stores, report link.Report) {
|
||||
for _, r := range replaced {
|
||||
said := SecretReplaced{Node: report.Node, Module: r.Module, Name: r.Name, Given: r.Given.UTC(),
|
||||
Sent: r.Machines, Why: givenWhy}
|
||||
log.Printf("replaced %q of %s on %s, given %s, with a value the mesh made: %s; sending %s",
|
||||
log.Printf("replaced %q of %s on %s, given %s, with a value the mesh made: %s; composing %s",
|
||||
r.Name, r.Module, report.Node, r.Given.UTC().Format(time.RFC3339), givenWhy, strings.Join(r.Machines, ", "))
|
||||
if err := sendTo(ctx, open, r.Machines); err != nil {
|
||||
// Composed (novox/hq ADR 0539 clause 5): the compositions that use it get a new revision, which each node's
|
||||
// want then names.
|
||||
if _, err := composeModules(ctx, open, r.Machines); err != nil {
|
||||
said.Sent, said.Unsent = nil, err.Error()
|
||||
log.Printf("the new %q of %s is sealed and not yet delivered to %s — the next push carries it: %v",
|
||||
r.Name, r.Module, strings.Join(r.Machines, ", "), err)
|
||||
log.Printf("the new %q of %s is sealed and its compositions on %s are not yet written — the next act that "+
|
||||
"composes them carries it: %v", r.Name, r.Module, strings.Join(r.Machines, ", "), err)
|
||||
}
|
||||
stateReplaced(ctx, said)
|
||||
}
|
||||
|
||||
@@ -315,7 +315,10 @@ func secretIssued(ctx context.Context, args []string) error {
|
||||
return err
|
||||
})
|
||||
},
|
||||
send: func(machines []string) error { return sendTo(ctx, open, machines) },
|
||||
send: func(machines []string) error {
|
||||
_, err := composeModules(ctx, open, machines)
|
||||
return err
|
||||
},
|
||||
ofTheVault: filledTheNewWay,
|
||||
vault: theVault(ctx),
|
||||
}
|
||||
|
||||
@@ -616,10 +616,11 @@ func throughTheTunnel(ctx context.Context, open *stores, node inventory.Node, ke
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := sendTo(ctx, open, []string{hub.Name}); err != nil {
|
||||
return nil, "", fmt.Errorf("%s was made a peer of the hub, and the hub could not be sent "+
|
||||
"it, so the tunnel would not answer — the token is not shown; issue it again once %s "+
|
||||
"can be pushed: %w", node.Name, hub.Name, err)
|
||||
// The hub's own composition carries the private network (novox/hq ADR 0539 clause 5), its want then names it.
|
||||
if _, err := composeModules(ctx, open, []string{hub.Name}); err != nil {
|
||||
return nil, "", fmt.Errorf("%s was made a peer of the hub, and the hub's composition could not be "+
|
||||
"written, so the tunnel would not answer — the token is not shown; issue it again once %s "+
|
||||
"composes: %w", node.Name, hub.Name, err)
|
||||
}
|
||||
// An address, not a name — nothing resolves before the machine has joined (novox/hq ADR 0004).
|
||||
return &token.Tunnel{
|
||||
|
||||
@@ -62,7 +62,9 @@ func ownGrantsOf(open *stores) ownGrantsCarrier {
|
||||
return ownGrantsCarrier{
|
||||
behind: func(ctx context.Context) (string, bool, error) { return brokerBehind(ctx, open, nil) },
|
||||
send: func(ctx context.Context, holder string) error {
|
||||
_, err := sendToEach(ctx, open, []string{holder})
|
||||
// The user list is a file of the bus module's composition (novox/hq ADR 0539 clause 7): composed, and the
|
||||
// assigner writes the bus node's want for that revision at once.
|
||||
_, err := composeModules(ctx, open, []string{holder})
|
||||
return err
|
||||
},
|
||||
}
|
||||
|
||||
@@ -435,9 +435,9 @@ func applyPending(ctx context.Context, open *stores, p inventory.PendingAssignme
|
||||
case strings.Contains(answer, "already runs"):
|
||||
note = fmt.Sprintf("%s already runs %s: the pending assignment is met", p.Node, p.Module)
|
||||
default:
|
||||
// The same as a person's assign: nothing is sent by this act.
|
||||
note = fmt.Sprintf("%s is assigned %s: build %s registered it (pending since %s); `push %s` sends it",
|
||||
p.Node, p.Module, by, clock(p.Since), p.Node)
|
||||
// The same as a person's assign: composed, and its want follows by the assigner's rules.
|
||||
note = fmt.Sprintf("%s is assigned %s: build %s registered it (pending since %s); composed, its want follows",
|
||||
p.Node, p.Module, by, clock(p.Since))
|
||||
}
|
||||
settled, serr := inv.SettlePending(ctx, p.ID, inventory.PendingApplying, state, note)
|
||||
if serr != nil {
|
||||
|
||||
@@ -142,20 +142,19 @@ func rotateCommand(ctx context.Context, args []string) error {
|
||||
}
|
||||
}
|
||||
|
||||
// **Both ends, in one send.** There is a window either way — a role's password changes on the
|
||||
// provider and the file changes on the consumer, and they cannot be simultaneous — so the
|
||||
// honest thing is to make it as short as the broker allows and to never leave it open across
|
||||
// a command boundary, where it depends on somebody's memory.
|
||||
fmt.Printf("\nsending to both ends:\n")
|
||||
if err := sendTo(ctx, open, machines); err != nil {
|
||||
// **Both ends, composed together** (novox/hq ADR 0539 clause 5). There is a window either way — a role's
|
||||
// password changes on the provider and the file changes on the consumer, and they cannot be simultaneous —
|
||||
// so both ends' compositions are written in one act, never left across a command boundary.
|
||||
fmt.Printf("\ncomposing both ends:\n")
|
||||
if _, err := composeModules(ctx, open, machines); err != nil {
|
||||
return fmt.Errorf(
|
||||
"%w\n\nThe old credentials are gone from the mesh and the new ones have not arrived. "+
|
||||
"Nothing on those machines has changed yet, so what is running keeps working "+
|
||||
"until the provider next applies. Fix the cause and run `push --behind`", err)
|
||||
"%w\n\nThe old credentials are gone from the mesh and the new compositions are not all written. "+
|
||||
"Nothing on those machines has changed yet, so what is running keeps working until each "+
|
||||
"node applies the composition its want names. Fix the cause and rotate again", err)
|
||||
}
|
||||
|
||||
fmt.Printf("\n%d machine(s) told. Until both ends have applied, a consumer whose password "+
|
||||
"changed cannot authenticate — `status` says who is still behind\n", len(machines))
|
||||
fmt.Printf("\n%d machine(s) composed. Until both ends have applied what their wants name, a consumer whose "+
|
||||
"password changed cannot authenticate — the delivery view says each machine's want and have\n", len(machines))
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -651,13 +651,14 @@ func rotateKept(ctx context.Context, node, module, name string, why handActFlags
|
||||
machines = []string{node}
|
||||
}
|
||||
if len(machines) == 1 {
|
||||
fmt.Printf("sending %s, so %s starts again on the new value:\n", node, module)
|
||||
fmt.Printf("composing %s, so %s starts again on the new value once its want names it:\n", node, module)
|
||||
} else {
|
||||
fmt.Printf("shared with every consumer; sending %s together:\n", strings.Join(machines, ", "))
|
||||
fmt.Printf("shared with every consumer; composing %s together:\n", strings.Join(machines, ", "))
|
||||
}
|
||||
if err := sendTo(ctx, open, machines); err != nil {
|
||||
return fmt.Errorf("%w\n\nThe new value is sealed and not yet delivered; what runs keeps the old "+
|
||||
"one until the machines next apply. Fix the cause and run `push --behind`", err)
|
||||
// Composed (novox/hq ADR 0539 clause 5): a new revision of the compositions that use it.
|
||||
if _, err := composeModules(ctx, open, machines); err != nil {
|
||||
return fmt.Errorf("%w\n\nThe new value is sealed and its compositions are not all written; what runs "+
|
||||
"keeps the old one. Fix the cause and move it again", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -191,11 +191,12 @@ func composeAnnouncedReads(ctx context.Context, open *stores) {
|
||||
if holder == "" || !behind {
|
||||
return
|
||||
}
|
||||
if _, err := sendToEach(ctx, open, []string{holder}); err != nil {
|
||||
fmt.Printf("states: the user list the states announced compose could not be sent to %s: %v\n", holder, err)
|
||||
// A file of the bus module's composition (novox/hq ADR 0539 clause 7), written into the bus node's want at once.
|
||||
if _, err := composeModules(ctx, open, []string{holder}); err != nil {
|
||||
fmt.Printf("states: the user list the states announced compose could not be composed for %s: %v\n", holder, err)
|
||||
return
|
||||
}
|
||||
fmt.Printf("states: sent %s, which holds the bus, the user list the states announced compose\n", holder)
|
||||
fmt.Printf("states: composed for %s, which holds the bus, the user list the states announced compose\n", holder)
|
||||
}
|
||||
|
||||
// notYetAnnounced are the owners some module reads the state of and that have announced nothing yet: what `status`
|
||||
|
||||
@@ -96,3 +96,20 @@ func ComposedReads(node string) ([]string, error) {
|
||||
}
|
||||
return append(readGrants(bucket, ""), readGrants(delivery.AssignerWantsBucket, "want."+node+".>")...), nil
|
||||
}
|
||||
|
||||
// VerbsTheNodeAgentAsks are the seat verbs a node's node-engine calls of its own machine while it applies a want (ADR
|
||||
// 0539 clause 16, ADR 0429 §14): its backup holder backs a data module up before it moves, and says when it was. Asked
|
||||
// of its own machine only, `mesh.seat.<seat>.tool.<verb>.<node>`.
|
||||
var VerbsTheNodeAgentAsks = []SeatVerb{{Seat: "node-backup", Verb: "now"}, {Seat: "node-backup", Verb: "backed-up"}}
|
||||
|
||||
// NodeAgentAsks are the subjects of VerbsTheNodeAgentAsks for one node.
|
||||
func NodeAgentAsks(node string) ([]string, error) {
|
||||
if !safeSubject.MatchString(node) {
|
||||
return nil, fmt.Errorf("%q cannot be part of a subject: a permission is a subject pattern, and this would widen it", node)
|
||||
}
|
||||
var out []string
|
||||
for _, v := range VerbsTheNodeAgentAsks {
|
||||
out = append(out, "mesh.seat."+v.Seat+".tool."+v.Verb+"."+node)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
@@ -47,3 +47,25 @@ func TestANodeReadsOnlyItsOwnCompositions(t *testing.T) {
|
||||
t.Errorf("a node name with a wildcard was granted")
|
||||
}
|
||||
}
|
||||
|
||||
// 1945 (for issue 1944): a node-engine asks its own machine's backup holder to back a data module up before it moves
|
||||
// and when it last was, and no other machine's.
|
||||
func TestANodeAgentAsksItsOwnBackupHolder(t *testing.T) {
|
||||
asks, err := NodeAgentAsks("ace")
|
||||
if err != nil || strings.Join(asks, " ") != "mesh.seat.node-backup.tool.now.ace mesh.seat.node-backup.tool.backed-up.ace" {
|
||||
t.Errorf("NodeAgentAsks(ace) = %v, %v", asks, err)
|
||||
}
|
||||
p, err := PermissionsFor(Principal{Kind: KindNode, Node: "ace", PasswordHash: "x"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
all := strings.Join(p.Publish, " ")
|
||||
for _, want := range append(asks, "$JS.API.DIRECT.GET.KV_composed-ace.>") {
|
||||
if !strings.Contains(all, want) {
|
||||
t.Errorf("a node-engine of ace may not publish %s", want)
|
||||
}
|
||||
}
|
||||
if strings.Contains(all, "node-backup.tool.now.*") || strings.Contains(all, "KV_composed-g14") {
|
||||
t.Errorf("a node-engine of ace reaches another machine: %v", p.Publish)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -865,6 +865,12 @@ func PermissionsFor(p Principal) (Permissions, error) {
|
||||
return Permissions{}, err
|
||||
}
|
||||
pub = append(pub, composedReads...)
|
||||
// And its own machine's backup holder, before a data module moves (ADR 0539 clause 16).
|
||||
backups, err := NodeAgentAsks(p.Node)
|
||||
if err != nil {
|
||||
return Permissions{}, err
|
||||
}
|
||||
pub = append(pub, backups...)
|
||||
// And, while the vault serves it, the vault's move-in in its own name and nobody else's (issue 676): the
|
||||
// values sealed to this machine, which only it opens, handed in sealed to the vault's key for the call.
|
||||
if p.MovesIntoTheVault {
|
||||
|
||||
+1
-1
@@ -38,7 +38,7 @@ accounts {
|
||||
subscribe: { allow: ["_INBOX.enrol.one.>"] }
|
||||
} }
|
||||
{ user: "node.one", password: "$2a$11$nnnnnnnnnnnnnnnnnnnnnn", permissions: {
|
||||
publish: { allow: ["$JS.ACK.NODES.one.>", "$JS.API.CONSUMER.CREATE.KV_composed-one.>", "$JS.API.CONSUMER.CREATE.KV_mesh-delivery-assigner_wants.*.$KV.mesh-delivery-assigner_wants.want.one.>", "$JS.API.CONSUMER.CREATE.KV_node-engine_have.*.$KV.node-engine_have.one.>", "$JS.API.CONSUMER.CREATE.OBJ_declaration-files-one.*.$O.declaration-files-one.C.*", "$JS.API.CONSUMER.DELETE.KV_composed-one.>", "$JS.API.CONSUMER.DELETE.KV_mesh-delivery-assigner_wants.>", "$JS.API.CONSUMER.DELETE.KV_node-engine_have.>", "$JS.API.CONSUMER.DELETE.OBJ_declaration-files-one.*", "$JS.API.CONSUMER.INFO.NODES.one", "$JS.API.DIRECT.GET.KV_composed-one.>", "$JS.API.DIRECT.GET.KV_mesh-delivery-assigner_wants.$KV.mesh-delivery-assigner_wants.want.one.>", "$JS.API.DIRECT.GET.KV_node-engine_have.$KV.node-engine_have.one.>", "$JS.API.DIRECT.GET.OBJ_declaration-files-one.$O.declaration-files-one.M.*", "$JS.API.STREAM.INFO.KV_composed-one", "$JS.API.STREAM.INFO.KV_mesh-delivery-assigner_wants", "$JS.API.STREAM.INFO.KV_node-engine_have", "$JS.FC.KV_composed-one.>", "$JS.FC.KV_mesh-delivery-assigner_wants.>", "$JS.FC.KV_node-engine_have.>", "$JS.FC.OBJ_declaration-files-one.>", "$KV.node-engine_have.one.>", "$SRV.PING.node-tools.one", "mesh.control.one.>"] }
|
||||
publish: { allow: ["$JS.ACK.NODES.one.>", "$JS.API.CONSUMER.CREATE.KV_composed-one.>", "$JS.API.CONSUMER.CREATE.KV_mesh-delivery-assigner_wants.*.$KV.mesh-delivery-assigner_wants.want.one.>", "$JS.API.CONSUMER.CREATE.KV_node-engine_have.*.$KV.node-engine_have.one.>", "$JS.API.CONSUMER.CREATE.OBJ_declaration-files-one.*.$O.declaration-files-one.C.*", "$JS.API.CONSUMER.DELETE.KV_composed-one.>", "$JS.API.CONSUMER.DELETE.KV_mesh-delivery-assigner_wants.>", "$JS.API.CONSUMER.DELETE.KV_node-engine_have.>", "$JS.API.CONSUMER.DELETE.OBJ_declaration-files-one.*", "$JS.API.CONSUMER.INFO.NODES.one", "$JS.API.DIRECT.GET.KV_composed-one.>", "$JS.API.DIRECT.GET.KV_mesh-delivery-assigner_wants.$KV.mesh-delivery-assigner_wants.want.one.>", "$JS.API.DIRECT.GET.KV_node-engine_have.$KV.node-engine_have.one.>", "$JS.API.DIRECT.GET.OBJ_declaration-files-one.$O.declaration-files-one.M.*", "$JS.API.STREAM.INFO.KV_composed-one", "$JS.API.STREAM.INFO.KV_mesh-delivery-assigner_wants", "$JS.API.STREAM.INFO.KV_node-engine_have", "$JS.FC.KV_composed-one.>", "$JS.FC.KV_mesh-delivery-assigner_wants.>", "$JS.FC.KV_node-engine_have.>", "$JS.FC.OBJ_declaration-files-one.>", "$KV.node-engine_have.one.>", "$SRV.PING.node-tools.one", "mesh.control.one.>", "mesh.seat.node-backup.call.backed-up.one.node~one", "mesh.seat.node-backup.call.now.one.node~one", "mesh.seat.node-backup.tool.backed-up.one", "mesh.seat.node-backup.tool.now.one"] }
|
||||
subscribe: { allow: ["_DELIVER.one", "_DELIVER.one.>", "_INBOX.node.one.>", "mesh.node.one.ask.hand-over", "mesh.node.one.ask.judge-directories", "mesh.node.one.ask.move-secret", "mesh.node.one.ask.report", "mesh.node.one.ask.setuid-search", "mesh.node.one.declare"] }
|
||||
allow_responses: { max: 1, ttl: "1m" }
|
||||
} }
|
||||
|
||||
Reference in New Issue
Block a user