package main import ( "encoding/json" "fmt" "git.novox.be/novox/mesh-sdk/go/catalogue" "os" "slices" "strings" "testing" ) // The shape ADR 0480 clauses 1, 2, 3 and 5 give the bus's manifest: the upstream image by digest, the mesh's two // programs mounted read-only under /opt, unpacked by archive resources the container names under restart-on // (the node-engine does not look inside a mounted directory), and the container the same one it was. func TestTheBussManifestMountsItsProgramsIntoTheUpstreamImage(t *testing.T) { type artifact struct { Name, Kind, From, Language, System, Binary string Env map[string]string Bus struct { Removes []string `json:"removes"` } `json:"bus"` } raw, err := os.ReadFile("../../module.json") if err != nil { t.Fatal(err) } var m struct { Data struct { Own []struct { ID string `json:"id"` Backup struct { Dump string `json:"dump"` } `json:"backup"` } `json:"own"` } `json:"data"` Provides []struct { Name string `json:"name"` Scope string `json:"scope"` Credential struct { Form string `json:"form"` } `json:"credential"` } `json:"provides"` Packages map[string]string `json:"packages"` Tools []string `json:"tools"` Emits []string `json:"emits"` Resources []map[string]any `json:"resources"` Build struct { On any `json:"on"` Artifacts []artifact `json:"artifacts"` } `json:"build"` } if err := json.Unmarshal(raw, &m); err != nil { t.Fatal(err) } // The removal grant the tests compose is the grants part's published package busgrants (series 0.1), which the // manifest names so the build resolves it (ADR 0634 §4). if m.Packages["busgrants"] != "0.1" { t.Errorf("packages = %v, want busgrants at 0.1", m.Packages) } // The bus provides "bus-state" beside "mesh-bus" (ADR 0614 §5): a module whose manifest keeps bus state requires it. provided := map[string]string{} for _, p := range m.Provides { provided[p.Name] = p.Scope + "/" + p.Credential.Form } for _, name := range []string{"mesh-bus", "bus-state"} { if provided[name] != "mesh/none" { t.Errorf("provision %q is %q: the bus provides it mesh-wide with no credential", name, provided[name]) } } if m.Build.On != nil { t.Error("build.on is declared: nothing is built on a base image any more") } artifacts := map[string]artifact{} for _, a := range m.Build.Artifacts { artifacts[a.Name] = a if a.Kind == "image" { t.Errorf("artifact %s is an image built from a Dockerfile (ADR 0452)", a.Name) } } if a := artifacts["server"]; a.Kind != "upstream" || !strings.Contains(a.From, "@sha256:") { t.Errorf("server is %+v: an upstream image pinned by digest", a) } if a := artifacts["snapshot"]; a.Kind != "bundle" || a.Language != "go" || a.System != "alpine" || a.From != "cmd/mesh-nats-snapshot" || a.Binary != "mesh-nats-snapshot" { t.Errorf("snapshot is %+v: a Go bundle of the module's own package, stamped for alpine", a) } if a := artifacts["entrypoint"]; a.Kind != "archive" || a.From != "program" { t.Errorf("entrypoint is %+v: an archive of program/", a) } if _, err := os.Stat("../../program/entrypoint.sh"); err != nil { t.Error(err) } if _, err := os.Stat("../../Dockerfile"); err == nil { t.Error("the Dockerfile is still there") } byID := map[string]map[string]any{} for _, r := range m.Resources { byID[r["id"].(string)] = r } server := byID["server"] if server["name"] != "mesh-broker-nats" || server["artifact"] != "server" { t.Errorf("the container is %v on artifact %v: it keeps its name, which the dump and the jails use", server["name"], server["artifact"]) } volumes, _ := server["volumes"].([]any) restart, _ := server["restart-on"].([]any) for _, p := range []struct{ resource, artifact, mount string }{ {"program-snapshot", "snapshot", ":/opt/mesh:ro"}, {"program-entrypoint", "entrypoint", ":/opt/mesh-entrypoint:ro"}, } { r := byID[p.resource] if r == nil || r["type"] != "archive" || r["artifact"] != p.artifact { t.Errorf("%s is %v: an archive resource unpacking %s", p.resource, r, p.artifact) continue } // Under a directory the module declares (ADR 0030: nothing is mounted from a path no resource covers). if dir, _, _ := strings.Cut(strings.TrimPrefix(r["path"].(string), "${dir:"), "}"); !strings.HasPrefix(r["path"].(string), "${dir:") || byID[dir] == nil || byID[dir]["type"] != "directory" { t.Errorf("%s is at %v: not under a directory the module declares", p.resource, r["path"]) } mounted := false for _, v := range volumes { if v == r["path"].(string)+p.mount { mounted = true } } if !mounted { t.Errorf("%s is not mounted read-only as %s", p.resource, p.mount) } named := false for _, v := range restart { named = named || v == p.resource } if !named { t.Errorf("%s is not in the container's restart-on: a new program would leave the old one running", p.resource) } } // The tools that change the bus (issue 1952 N1-N3): listed, their audit events declared, and the credential // they dial with the module's own secret `broker`. for _, want := range []string{"nats_consumer_remove", "nats_bucket_remove", "nats_stream_remove", "nats_bucket_expiry"} { if !slices.Contains(m.Tools, want) { t.Errorf("tools do not list %s", want) } } for _, want := range []string{"consumer-removed", "bucket-keys-removed", "stream-removed", "bucket-expiry-set"} { if !slices.Contains(m.Emits, want) { t.Errorf("emits do not list %s: the removal tools say it as the audit line", want) } } for _, a := range m.Build.Artifacts { // Issue 1126 B5 (ADR 0590 §6): the tools may remove the controller's leftovers, by the one word for them, and // nothing else of the controller's by a word of its own. if a.Name == "tools" && !slices.Equal(a.Bus.Removes, []string{"controller-leftovers"}) { t.Errorf("the tools' bus removes %v: the controller's leftovers by their one word, controller-leftovers", a.Bus.Removes) } if a.Name == "tools" && a.Env["NATS_TOOLS_CREDENTIAL"] != "${dir:tools-secret}/broker-nats-tools" { t.Errorf("the tools' credential is %q: the process nats-tools' own secret broker-nats-tools, under a word of the bundle's own (the runtime strips MESH_BROKER_FILE)", a.Env["NATS_TOOLS_CREDENTIAL"]) } } // The bus module provides bus state (design 25 § 12.2-12.3, ADR 0595 §4-§6; CAT-N1): its keeper is a process of // its own on its own account (own secret broker-keeper, ADR 0436 §1), creating only bus state; it hears the indexer's // manifest-accepted, keeps what it heard in its own old state `asked` (ADR 0595 §10) and the persons' requests in // `requests`; its nine events are its own (ADR 0620 §2), on its own stream kept 30 days at 256 MiB; the seat's four // reading verbs are served, its four acting verbs are the keeper's own work and no other account's; its health is // the keeper's own tool, beside the server's tcp check. var nm struct { Claims []struct { Name string `json:"name"` Serves []string `json:"serves"` } `json:"claims"` Consumes []string `json:"consumes"` Invokes []string `json:"invokes"` Uses []string `json:"uses"` OwnSecrets map[string]json.RawMessage `json:"own-secrets"` Streams []struct { Name string `json:"name"` Events []string `json:"events"` MaxAgeDays int `json:"max-age-days"` MaxMiB int `json:"max-mib"` } `json:"streams"` State []struct { Name string `json:"name"` ReadOnly bool `json:"read-only"` } `json:"state"` } if err := json.Unmarshal(raw, &nm); err != nil { t.Fatal(err) } if len(nm.Claims) != 1 || nm.Claims[0].Name != "mesh-broker" || !slices.Equal(nm.Claims[0].Serves, ReadingVerbs) { t.Errorf("claims are %+v: mesh-broker, serving its reading verbs %v alone", nm.Claims, ReadingVerbs) } if !slices.Equal(nm.Consumes, []string{"mesh-catalog.manifest-accepted"}) { t.Errorf("consumes %v: the seat mesh-catalog's manifest-accepted alone (ADR 0595 §4)", nm.Consumes) } if AcceptedSubject != "mesh.seat.mesh-catalog.event.manifest-accepted" { t.Errorf("the keeper's durable consumer filters on %s, not on the seat mesh-catalog's event", AcceptedSubject) } for _, want := range []string{"mesh-delivery-indexer.indexer_bus_state", "seat:mesh-hand-acts.hand-act"} { if !slices.Contains(nm.Invokes, want) { t.Errorf("invokes %v lack %s", nm.Invokes, want) } } if !slices.Contains(nm.Uses, "operator-channel") { t.Errorf("uses %v: the operator channel, for the warrant of recreate and drop", nm.Uses) } if len(nm.Streams) != 1 || nm.Streams[0].Name != "broker-events" || !slices.Equal(nm.Streams[0].Events, Events) || nm.Streams[0].MaxAgeDays != 30 || nm.Streams[0].MaxMiB != 256 { t.Errorf("streams are %+v: broker-events carrying %v, 30 days, 256 MiB", nm.Streams, Events) } for _, e := range Events { if slices.Contains(m.Emits, e) { t.Errorf("emits lists %s, which its stream carries: an event is named in one place (ADR 0363)", e) } } var states, readOnly []string for _, s := range nm.State { states = append(states, s.Name) if s.ReadOnly { readOnly = append(readOnly, s.Name) } } // spent, the warrants the keeper spent, is written by the keeper alone and only read by the tools (review round 2, // security F2: the tool runner's account, which writes requests, may never delete a spend record). if !slices.Equal(states, []string{"asked", "requests", "spent"}) || !slices.Equal(readOnly, []string{"spent"}) { t.Errorf("state is %v, read-only %v: asked and requests, old sections (ADR 0595 §10), and spent, read-only", states, readOnly) } if _, ok := nm.OwnSecrets["broker-keeper"]; !ok { t.Error("own-secrets lack broker-keeper, the keeper's own account (ADR 0436 §1)") } keeper := byID["keeper"] bus, _ := keeper["bus"].(map[string]any) health, _ := keeper["health"].(map[string]any) if keeper["type"] != "process" || keeper["artifact"] != "tools" || fmt.Sprint(keeper["run"]) != "[./nats-tools keep]" || fmt.Sprint(bus["state"]) != "[asked requests spent]" || health["kind"] != "tool" || health["tool"] != "nats_keeper_health" { t.Errorf("the keeper is %v: a process of the tools bundle, run as keep, keeping asked, requests and spent, judged by nats_keeper_health", keeper) } // Its bus declares keeps, the one key more ADR 0626 §1 allows it, read through the SDK as the grant composition reads it. var whole catalogue.Manifest if err := json.Unmarshal(raw, &whole); err != nil { t.Fatal(err) } for _, st := range whole.State { if (st.Name == "spent") != st.ReadOnly { t.Errorf("the SDK reads %s read-only %v: spent alone is read-only", st.Name, st.ReadOnly) } } if b := whole.ProcessBuses()["keeper"]; !b.Keeps { t.Errorf("the keeper's bus declares no keeps, so it would be composed no bus state to keep (ADR 0626 §1): %+v", b) } if h, _ := server["health"].(map[string]any); h["kind"] != "tcp" || h["endpoint"] != "bus" { t.Errorf("the server's health is %v: tcp on bus, the check the keeper's tool check stands beside", server["health"]) } served := map[string]bool{} for _, tl := range allTools() { served[tl.Name] = true } for _, name := range m.Tools { if !served[name] { t.Errorf("the manifest lists the tool %s, which the bundle does not serve", name) } } for _, verb := range ReadingVerbs { if !served["mesh-broker."+verb] { t.Errorf("the bundle does not serve the seat verb mesh-broker.%s", verb) } } args, _ := server["args"].([]any) if len(args) != 2 || args[0] != "sh" || args[1] != "/opt/mesh-entrypoint/entrypoint.sh" { t.Errorf("args are %v: the entrypoint run through sh, so no execute bit is relied on", args) } for _, d := range m.Data.Own { if d.ID == "jetstream" && !strings.Contains(d.Backup.Dump, "docker exec -i mesh-broker-nats /opt/mesh/mesh-nats-snapshot snapshot") { t.Errorf("the dump is %q: it calls the mounted program", d.Backup.Dump) } } }