package catalogue import ( "encoding/json" "strings" "testing" ) // A module may need several values from one provider that gives one per pair (novox/hq // 04-ISSUES/069, ADR 0094): `secrets` maps a requirement to several files under local names, and // each local name is a pair credential of its own — its own need, its own file, its own holder. const twoSecrets = `{"module":"ca","version":"1","requires":["secret"], "secrets":{"secret":{"root-key":"/var/lib/ca/root.key","root-pass":"/var/lib/ca/root.pass"}}, "resources":[{"id":"state","type":"directory","path":"/var/lib/ca","mode":"0700"}]}` func TestSecretsReadBothShapesAndWriteThemBack(t *testing.T) { m, err := ParseManifest([]byte(twoSecrets)) if err != nil { t.Fatal(err) } files := m.SecretFiles("secret") if len(files) != 2 || files[0].Local != "root-key" || files[1].Path != "/var/lib/ca/root.pass" { t.Fatalf("two files under local names, in order: %+v", files) } plain, err := ParseManifest([]byte(`{"module":"app","version":"1","requires":["secret"],"secrets":{"secret":"/var/lib/app/secret"}}`)) if err != nil { t.Fatal(err) } if got := plain.SecretFiles("secret"); len(got) != 1 || got[0].Local != "" || got[0].Path != "/var/lib/app/secret" { t.Fatalf("the plain shape is one file with no local name: %+v", got) } // Written back in the shape it was read, so a built manifest keeps its local names. raw, err := json.Marshal(m) if err != nil { t.Fatal(err) } again, err := ParseManifest(raw) if err != nil { t.Fatalf("what was written does not read: %v\n%s", err, raw) } if len(again.SecretFiles("secret")) != 2 { t.Fatalf("the local names did not survive a round trip:\n%s", raw) } } func TestALocalNameMayNotCollideWithWhatTheModuleAlreadyCallsSomething(t *testing.T) { for _, bad := range []string{ // One of the module's own secrets. `{"module":"ca","version":"1","requires":["secret"],"own-secrets":{"root-key":"/var/lib/ca/own"}, "secrets":{"secret":{"root-key":"/var/lib/ca/root.key"}}}`, // Something it requires. `{"module":"ca","version":"1","requires":["secret","postgres-database"], "secrets":{"secret":{"postgres-database":"/var/lib/ca/x"}}}`, // Not a usable name. `{"module":"ca","version":"1","requires":["secret"],"secrets":{"secret":{"Root Key":"/var/lib/ca/x"}}}`, // A relative path. `{"module":"ca","version":"1","requires":["secret"],"secrets":{"secret":{"root-key":"root.key"}}}`, } { if _, err := ParseManifest([]byte(bad)); err == nil { t.Errorf("accepted:\n%s", bad) } } } func vaultAndCA() map[string]Manifest { ca, _ := ParseManifest([]byte(twoSecrets)) vault := Manifest{Module: "mesh-vault", Version: "1", Provides: FromAnywhere("secret"), Grants: map[string]string{"secret": "/var/lib/vault/grants"}, Receives: map[string]string{"secret": "/var/lib/vault/grants/mesh.json"}} return shelf(vault, ca) } func TestEachLocalNameIsANeedAFileAndAHolderOfItsOwn(t *testing.T) { got, err := Resolve(vaultAndCA(), []string{"mesh-vault", "ca"}, workstation(), World{}) if err != nil { t.Fatal(err) } var locals []string for _, n := range got.Needs { if n.Name == "secret" && n.For == "ca" { locals = append(locals, n.Local) } } if strings.Join(locals, ",") != "root-key,root-pass" { t.Fatalf("two secrets from one provider are two needs: %v", got.Needs) } for i := range got.Needs { got.Needs[i].Sealed = "sealed-" + got.Needs[i].Local } out, err := got.Declaration(Rendering{}) if err != nil { t.Fatal(err) } seen := map[string]string{} for _, r := range out { if r["type"] == "file" && strings.HasPrefix(r["path"].(string), "/var/lib/ca/root.") { seen[r["id"].(string)] = r["sealed"].(string) } } if seen["ca."+SecretID("root-key")] != "sealed-root-key" || seen["ca."+SecretID("root-pass")] != "sealed-root-pass" { t.Fatalf("each local name is its own file with its own credential: %v", seen) } } func TestAProviderSeesEachLocalNameAsAHolderOfItsOwn(t *testing.T) { r := Resolution{Modules: []Manifest{vaultAndCA()["mesh-vault"]}} got, err := r.contributions(SettingsBy{}, []Grant{ {Provision: "secret", Consumer: "workstation", From: "ca", Local: "root-key", Sealed: "x"}, {Provision: "secret", Consumer: "workstation", From: "ca", Local: "root-pass", Sealed: "y"}, }, map[string]string{"secret": "/var/lib/vault/grants"}) if err != nil { t.Fatal(err) } given := got["secret"] if len(given) != 2 { t.Fatalf("two holders: %+v", given) } if given[0].As != "mesh_workstation_ca_root_key" && given[0].As != "mesh_workstation_ca_root-key" { t.Fatalf("the holder is the consumer's identity with the local name after it: %q", given[0].As) } if given[0].Secret == given[1].Secret { t.Fatalf("two holders share one file on the provider: %q", given[0].Secret) } }