The lab's vault refused a declaration naming two files with one identity: the two secrets of one consumer. The holder's suffix is in the resource id and the path now.
162 lines
6.2 KiB
Go
162 lines
6.2 KiB
Go
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"}}
|
|
// A second consumer of the same provision that keeps ONE file, mentioned before the one that
|
|
// keeps two: the lab found the expansion done on the first module to mention the provision,
|
|
// and the second consumer given one credential and no file.
|
|
cache := Manifest{Module: "cache", Version: "1", Requires: []string{"secret"},
|
|
Secrets: map[string]string{"secret": "/var/lib/cache/secret"}}
|
|
return shelf(vault, cache, ca)
|
|
}
|
|
|
|
func TestEachLocalNameIsANeedAFileAndAHolderOfItsOwn(t *testing.T) {
|
|
got, err := Resolve(vaultAndCA(), []string{"mesh-vault", "cache", "ca"}, workstation(), World{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var locals, cacheLocals []string
|
|
for _, n := range got.Needs {
|
|
if n.Name == "secret" && n.For == "ca" {
|
|
locals = append(locals, n.Local)
|
|
}
|
|
if n.Name == "secret" && n.For == "cache" {
|
|
cacheLocals = append(cacheLocals, n.Local)
|
|
}
|
|
}
|
|
if strings.Join(locals, ",") != "root-key,root-pass" {
|
|
t.Fatalf("two secrets from one provider are two needs: %v", got.Needs)
|
|
}
|
|
if len(cacheLocals) != 1 || cacheLocals[0] != "" {
|
|
t.Fatalf("the one-file consumer keeps one need with no local name: %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)
|
|
}
|
|
}
|
|
|
|
// And on the provider's machine, two files with two ids — the lab's first run had the declaration
|
|
// refused for two resources with one identity.
|
|
func TestAProviderKeepsOneFilePerHolder(t *testing.T) {
|
|
got, err := Resolve(vaultAndCA(), []string{"mesh-vault", "cache", "ca"}, workstation(), World{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
out, err := got.Declaration(Rendering{Grants: []Grant{
|
|
{Provision: "secret", Consumer: "workstation", From: "ca", Local: "root-key", Sealed: "x"},
|
|
{Provision: "secret", Consumer: "workstation", From: "ca", Local: "root-pass", Sealed: "y"},
|
|
}})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
ids := map[string]string{}
|
|
for _, r := range out {
|
|
if id, _ := r["id"].(string); strings.Contains(id, "grant-secret") {
|
|
ids[id] = r["path"].(string)
|
|
}
|
|
}
|
|
if len(ids) != 2 {
|
|
t.Fatalf("two holders are two grant files: %v", ids)
|
|
}
|
|
}
|