diff --git a/modules/mesh-vault/cmd/mesh-vault/nodekey_test.go b/modules/mesh-vault/cmd/mesh-vault/nodekey_test.go index e266dc3d..5a0c738c 100644 --- a/modules/mesh-vault/cmd/mesh-vault/nodekey_test.go +++ b/modules/mesh-vault/cmd/mesh-vault/nodekey_test.go @@ -5,6 +5,8 @@ package main // key (or a first one) raises `secret-changed` for the machine's core secrets, the same key says nothing. import ( + "git.novox.be/novox/mesh-sdk/go/seal" + "encoding/json" "os" "path/filepath" @@ -100,14 +102,17 @@ authorization { users = [ { user: "novox.mesh-vault", password: "pw", permissions: { publish: { deny: [">"] } - subscribe: { allow: ["mesh.seat.mesh-vault.call.node-key.*"] } + subscribe: { allow: ["mesh.seat.mesh-vault.call.node-key.*", "mesh.seat.mesh-vault.call.own.*"] } allow_responses: true } } { user: "node.ace", password: "pw", permissions: { - publish: { allow: ["mesh.seat.mesh-vault.call.node-key.node_ace"] } + publish: { allow: ["mesh.seat.mesh-vault.call.node-key.node_ace", "mesh.seat.mesh-vault.call.own.node_ace"] } subscribe: { allow: ["_INBOX.node.ace.>"] } } } { user: "node.g14", password: "pw", permissions: { - publish: { allow: ["mesh.seat.mesh-vault.call.node-key.node_g14"] } + publish: { allow: ["mesh.seat.mesh-vault.call.node-key.node_g14", "mesh.seat.mesh-vault.call.own.node_g14"] } subscribe: { allow: ["_INBOX.node.g14.>"] } } } + { user: "controller", password: "pw", permissions: { + publish: { allow: ["mesh.seat.mesh-vault.call.own.controller"] } + subscribe: { allow: ["_INBOX.controller.>"] } } } ] } ` @@ -135,39 +140,47 @@ func TestOnARealBusANodeEngineStatesOnlyItsOwnKey(t *testing.T) { t.Cleanup(s.Shutdown) v := newVault(t, nil) - tool := toolNamed(t, definitions(&Env{Vault: v.Vault}), Seat+".node-key") + tools := map[string]toolDef{"node-key": toolNamed(t, definitions(&Env{Vault: v.Vault}), Seat+".node-key"), + "own": toolNamed(t, definitions(&Env{Vault: v.Vault}), Seat+".own")} vaultConn := connectAs(t, s.ClientURL(), "novox.mesh-vault") - if _, err := vaultConn.Subscribe("mesh.seat.mesh-vault.call.node-key.*", func(m *nats.Msg) { + serve := func(m *nats.Msg) { parts := strings.Split(m.Subject, ".") var args map[string]any _ = json.Unmarshal(m.Data, &args) answer := busAnswer{} - if got, err := tool.runAs(untoken(parts[len(parts)-1]), args); err != nil { + if got, err := tools[parts[len(parts)-2]].runAs(untoken(parts[len(parts)-1]), args); err != nil { answer.Error = err.Error() } else { answer.Result, _ = json.Marshal(got) } raw, _ := json.Marshal(answer) _ = m.Respond(raw) - }); err != nil { - t.Fatal(err) + } + for _, verb := range []string{"node-key", "own"} { + if _, err := vaultConn.Subscribe("mesh.seat.mesh-vault.call."+verb+".*", serve); err != nil { + t.Fatal(err) + } } _ = vaultConn.Flush() - say := func(user, subject, key string) (string, error) { - raw, _ := json.Marshal(map[string]string{"key": key}) + call := func(user, subject string, args any) (json.RawMessage, error) { + raw, _ := json.Marshal(args) msg, err := connectAs(t, s.ClientURL(), user).Request(subject, raw, 3*time.Second) if err != nil { - return "", err + return nil, err } var a busAnswer _ = json.Unmarshal(msg.Data, &a) if a.Error != "" { - return a.Error, errString(a.Error) + return nil, errString(a.Error) } - return "", nil + return a.Result, nil } - aceKey, _ := aSealingKey(t) + say := func(user, subject, key string) (string, error) { + _, err := call(user, subject, map[string]string{"key": key}) + return "", err + } + aceKey, acePriv := aSealingKey(t) g14Key, _ := aSealingKey(t) if _, err := say("node.ace", "mesh.seat.mesh-vault.call.node-key.node_ace", aceKey); err != nil { t.Fatal(err) @@ -188,4 +201,32 @@ func TestOnARealBusANodeEngineStatesOnlyItsOwnKey(t *testing.T) { if got, held, _ := v.Store.NodeKey("g14"); !held || got != g14Key { t.Errorf("g14's key is %q, %v", got, held) } + + // Issue 1126 (ADR 0547 §2, ADR 0550 §5): `own` on the same bus. node.ace asks its own and gets its module's secret + // sealed to the key it stated, opening with its key alone; g14 cannot ask in ace's name, and asking in its own gets + // none of ace's; the controller is refused, as no node-engine. + if _, err := moveIn(t, v, "node.ace", MoveOf{Node: "ace", Module: "web", Name: "password", Origin: "made", Generation: "g-1", + SealingKey: aceKey}, "ace-web-pw"); err != nil { + t.Fatal(err) + } + raw, err := call("node.ace", "mesh.seat.mesh-vault.call.own.node_ace", map[string]any{}) + var own OwnAnswer + if err != nil || json.Unmarshal(raw, &own) != nil || own.Node != "ace" || len(own.Own) != 1 { + t.Fatalf("ace's own over the bus: %s, %v", raw, err) + } + if value, err := seal.Open(own.Own[0].Sealed, acePriv); err != nil || string(value) != "ace-web-pw" { + t.Errorf("ace's secret does not open with ace's key: %q, %v", value, err) + } + if strings.Contains(string(raw), "ace-web-pw") { + t.Error("ace's secret crossed the bus in the clear") + } + if _, err := call("node.g14", "mesh.seat.mesh-vault.call.own.node_ace", map[string]any{}); err == nil { + t.Error("node.g14 asked ace's own in ace's name") + } + if raw, err := call("node.g14", "mesh.seat.mesh-vault.call.own.node_g14", map[string]any{}); err != nil || strings.Contains(string(raw), "web") { + t.Errorf("g14 asking its own was answered %s, %v", raw, err) + } + if _, err := call("controller", "mesh.seat.mesh-vault.call.own.controller", map[string]any{}); err == nil || !strings.Contains(err.Error(), "not a node-engine") { + t.Errorf("the controller asking own: %v", err) + } }