package resolution import ( "encoding/json" "strings" "testing" mcat "git.novox.be/novox/mesh-sdk/go/catalogue" ) // novox/hq ADR 0310 §5: the holder is told, by the composition, where its node keeps a secret — the directory // of every module's own secret, every credential file and grant directory, the vault's directories and the // place the operator-sealed secrets are kept — and no other module is told. func TestTheHolderOfNodeReadsIsToldWhereItsNodeKeepsASecret(t *testing.T) { with := mcat.Rendering{ArtifactStore: "anchor.internal:5101", Needed: map[string]map[string]string{mcat.RuntimeModule: {"broker": "sealed-credential"}, "reads": {"broker": "own"}, "desk": {"broker": "own"}}} goRuntime := mcat.Manifest{Module: mcat.RuntimeModule, Version: "1", OwnSecrets: mcat.OwnSecrets{"broker": {Path: "/var/lib/mesh/" + mcat.RuntimeModule + "/broker"}}, Build: &mcat.Build{Artifacts: []mcat.Artifact{{Name: "runtime", Kind: mcat.ArtifactBundle, Language: "go", System: "arch", From: "cmd/node-tools"}}}} goRuntime, err := goRuntime.Resolve([]mcat.Built{{Name: "runtime", Kind: mcat.ArtifactBundle, Reference: mcat.ArtifactStoreScheme + mcat.RuntimeModule + "/runtime/blobs/" + bundleDigest, Digest: bundleDigest}}) if err != nil { t.Fatal(err) } reads := aToolsModule(t, "reads", "tools/index.js") reads.RunsAs, reads.SecretsOwner = "mesh-reads", "mesh-reads" reads.Claims = []mcat.Claim{{Name: mcat.ReadsSeat}} reads.OwnSecrets = mcat.OwnSecrets{"broker": {Path: "/var/lib/mesh-reads/broker"}} desk := aToolsModule(t, "desk", "tools/index.js") desk.RunsAs, desk.SecretsOwner = "desk", "desk" desk.OwnSecrets = mcat.OwnSecrets{"broker": {Path: "/var/lib/desk/broker"}} // The vault as the catalogue declares it: its directories have no path of their own, and the node places them. vault, err := placedManifest(catalogueResolved(t, "mesh-vault", mcat.Built{Name: "code", Kind: mcat.ArtifactBundle, Reference: mcat.ArtifactStoreScheme + "mesh-vault/code/blobs/" + bundleDigest, Digest: bundleDigest}), with) if err != nil { t.Fatal(err) } app := mcat.Manifest{Module: "app", Version: "1", Secrets: map[string]string{"postgres-database": "/etc/app/db.secret"}, OwnSecrets: mcat.OwnSecrets{"token": {Path: "/home/ops/.config/app/token"}}} r := mcat.Resolution{Node: "anchor", Account: "ops", Modules: []mcat.Manifest{reads, desk, vault, app, goRuntime}} owns, err := resolutionOwnRuntimes(r, with) if err != nil { t.Fatal(err) } var given map[string]map[string]string for _, p := range owns { if p["id"] == "reads."+mcat.OwnRuntimeID() { if err := json.Unmarshal([]byte(p["env"].(map[string]string)[mcat.RuntimeToolEnv]), &given); err != nil { t.Fatal(err) } } if p["id"] == "desk."+mcat.OwnRuntimeID() && strings.Contains(p["env"].(map[string]string)[mcat.RuntimeToolEnv], mcat.ReadsSecretPlaces) { t.Error("a module not holding node-reads is told the node's secret places") } } var places []string if err := json.Unmarshal([]byte(given["reads"][mcat.ReadsSecretPlaces]), &places); err != nil { t.Fatalf("the holder is not told its node's secret places: %v (%v)", err, given) } want := []string{"/etc/app/db.secret", "/home/ops/.config/app", "/var/lib/desk", "/var/lib/mesh-reads", "/var/lib/mesh-vault", "/var/lib/mesh/" + mcat.RuntimeModule} if strings.Join(places, " ") != strings.Join(want, " ") { t.Errorf("secret places:\n got %v\nwant %v", places, want) } } // The review of #257: a secret place the controller cannot resolve fails the composition, naming the module, // rather than leaving the list one place shorter. func TestASecretPlaceThatCannotBeResolvedFailsTheComposition(t *testing.T) { r := mcat.Resolution{Node: "anchor", Modules: []mcat.Manifest{ {Module: "app", Version: "1", Keeps: "${dir:keep}/sealed"}, }} if _, err := resolutionSecretPlaces(r, mcat.Rendering{}); err == nil || !strings.Contains(err.Error(), "app") { t.Errorf("an unresolved place was dropped: %v", err) } r.Modules[0].Keeps = "relative/place" if _, err := resolutionSecretPlaces(r, mcat.Rendering{}); err == nil { t.Error("a relative place was dropped") } }