mesh/merge-gate head bff1ac14a198 merged into trunk 49c9002c04d3 beside mesh-controller#355@8c381172 — pass: builds mesh-host → ace, g14, novox, shanks; no…
mesh/repo-check head bff1ac14a198 merged into trunk 49c9002c04d3 beside mesh-controller#355@8c381172 — pass: its merge-check.sh passed
mesh/delivery delivered
mesh/delivery-group group release/57-config-and-provisioning delivered: every member is delivered
155 lines
4.7 KiB
Go
155 lines
4.7 KiB
Go
package main
|
|
|
|
// Issue 755's proof on a real bus (novox/hq ADR 0349 §16): a core credential the vault sealed to this machine crosses
|
|
// the bus inside the declaration, which the controller carries and cannot open, and this engine opens it with its
|
|
// sealing key and writes the value. A listener on every subject keeps every byte that crossed; the value is never
|
|
// among them. Run against the throwaway bus merge-check.sh starts (MESH_TEST_NATS).
|
|
|
|
import (
|
|
"context"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/seal"
|
|
"github.com/nats-io/nats.go"
|
|
|
|
"github.com/novox/mesh-host/internal/apply"
|
|
"github.com/novox/mesh-host/internal/declaration"
|
|
"github.com/novox/mesh-host/internal/identity"
|
|
"github.com/novox/mesh-host/internal/link"
|
|
"github.com/novox/mesh-host/internal/store"
|
|
"github.com/novox/mesh-host/internal/system"
|
|
)
|
|
|
|
const coreCredential = "the-provisioner-clients-bus-password-2025"
|
|
|
|
func TestNatsTheEngineFillsACoreCredentialFromTheVaultsBoxWithNoValueInTheClear(t *testing.T) {
|
|
url := os.Getenv("MESH_TEST_NATS")
|
|
if url == "" {
|
|
t.Skip("MESH_TEST_NATS unset")
|
|
}
|
|
// This machine's sealing key, where the engine reads it.
|
|
state := filepath.Join(t.TempDir(), "state.json")
|
|
key, err := identity.GenerateSealingKey()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(identity.SealingKeyPath(state), []byte(key.Private), 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Every byte that crosses.
|
|
tap, err := nats.Connect(url)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer tap.Close()
|
|
var mu sync.Mutex
|
|
var crossed []string
|
|
if _, err := tap.Subscribe(">", func(m *nats.Msg) {
|
|
mu.Lock()
|
|
crossed = append(crossed, m.Subject+" "+string(m.Data))
|
|
mu.Unlock()
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
_ = tap.Flush()
|
|
|
|
// The engine hears its declaration on its own subject.
|
|
const node = "vaultbox"
|
|
engine, err := nats.Connect(url)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer engine.Close()
|
|
heard, err := engine.SubscribeSync(link.DeclareSubject(node))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
_ = engine.Flush()
|
|
|
|
// The vault's answer to the controller: the value in the ADR 0206 box, sealed to this machine's key. The
|
|
// controller carries it in the declaration as it is.
|
|
pub, err := key.PublicPEM()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
box, err := seal.Seal(coreCredential, pub)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
path := filepath.Join(t.TempDir(), "broker")
|
|
body, _ := json.Marshal(map[string]any{"declaration": 1, "resources": []map[string]any{
|
|
{"id": "need-provisioner-broker", "type": "file", "path": path, "vault-sealed": box}}})
|
|
controller, err := nats.Connect(url)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer controller.Close()
|
|
if err := controller.Publish(link.DeclareSubject(node), body); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
_ = controller.Flush()
|
|
|
|
msg, err := heard.NextMsg(5 * time.Second)
|
|
if err != nil {
|
|
t.Fatalf("the declaration did not reach the engine: %v", err)
|
|
}
|
|
d, err := declaration.Parse(msg.Data)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
sys, err := system.For("arch")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, _, err := apply.Apply(context.Background(), sys, d, store.State{}, store.OriginCarried,
|
|
func(context.Context, string, ...string) (string, error) { return "", nil }, nil, sealOpener(state)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
on, err := os.ReadFile(path)
|
|
if err != nil || string(on) != coreCredential {
|
|
t.Fatalf("the engine wrote %q, %v", on, err)
|
|
}
|
|
|
|
_ = tap.Flush()
|
|
time.Sleep(100 * time.Millisecond)
|
|
mu.Lock()
|
|
all := strings.Join(crossed, "\n")
|
|
mu.Unlock()
|
|
if !strings.Contains(all, link.DeclareSubject(node)) || !strings.Contains(all, "vault-sealed") {
|
|
t.Fatalf("the listener heard nothing of the declaration:\n%s", all)
|
|
}
|
|
for _, form := range []string{coreCredential, base64.StdEncoding.EncodeToString([]byte(coreCredential))} {
|
|
if strings.Contains(all, form) {
|
|
t.Fatal("the core credential crossed the bus in the clear")
|
|
}
|
|
}
|
|
}
|
|
|
|
// The engine opens a value the controller sealed (the NaCl box) as before, and the vault's box, with one key.
|
|
func TestTheEnginesOpenerOpensBothBoxes(t *testing.T) {
|
|
state := filepath.Join(t.TempDir(), "state.json")
|
|
key, _ := identity.GenerateSealingKey()
|
|
if err := os.WriteFile(identity.SealingKeyPath(state), []byte(key.Private), 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
open := sealOpener(state)
|
|
nacl, _ := identity.Seal(key.Public, []byte("old"))
|
|
if got, err := open(nacl); err != nil || string(got) != "old" {
|
|
t.Errorf("the controller's box opened to %q, %v", got, err)
|
|
}
|
|
pub, _ := key.PublicPEM()
|
|
box, _ := seal.Seal("new", pub)
|
|
raw, _ := json.Marshal(box)
|
|
if got, err := open(string(raw)); err != nil || string(got) != "new" {
|
|
t.Errorf("the vault's box opened to %q, %v", got, err)
|
|
}
|
|
}
|