A module waiting for the operator's secret failed its first-node gate, held every later walk and was said as 'nothing for you to do'. The node-engine's new waiting state is checked against the manifest and the secrets given, read by the gate as a wait for a person, and raised as needs-operator naming the act. A secret family gives each part its own one-line secret, which the mesh never makes, so the desk prompt can take each password.
101 lines
4.1 KiB
Go
101 lines
4.1 KiB
Go
package catalogue
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// A secret family (novox/hq ADR 0283): one own secret per part the settings name, issued outside the mesh, each
|
|
// given by its full name, never made by the mesh.
|
|
|
|
func familyManifest(t *testing.T, ownSecrets string) (Manifest, error) {
|
|
t.Helper()
|
|
return ParseManifest([]byte(`{"module":"mounts","version":"1","own-secrets":{` + ownSecrets + `}}`))
|
|
}
|
|
|
|
func TestAFamilyIsDeclaredIssuedOutsideWithOneStar(t *testing.T) {
|
|
m, err := familyManifest(t, `"smb-password-*":{"path":"/var/lib/mounts/smb-password-*.secret","issued-by":"outside"}`)
|
|
if err != nil {
|
|
t.Fatalf("a well-formed family was refused: %v", err)
|
|
}
|
|
if got := m.OwnSecrets.Families(); len(got) != 1 || got[0] != "smb-password-*" {
|
|
t.Fatalf("families: %v", got)
|
|
}
|
|
if len(m.OwnSecrets.Plain()) != 0 {
|
|
t.Fatalf("a family counted as a secret declared by name: %v", m.OwnSecrets.Plain())
|
|
}
|
|
}
|
|
|
|
func TestAMalformedFamilyIsRefused(t *testing.T) {
|
|
for name, c := range map[string]struct{ own, says string }{
|
|
"made by the mesh": {`"smb-password-*":{"path":"/s/smb-password-*.secret","taken":"at-start"}`, "issued-by"},
|
|
"no star in its path": {`"smb-password-*":{"path":"/s/smb-password.secret","issued-by":"outside"}`,
|
|
"exactly one *"},
|
|
"two stars in its path": {`"smb-password-*":{"path":"/s/*/smb-password-*.secret","issued-by":"outside"}`,
|
|
"exactly one *"},
|
|
"a star inside a name": {`"smb*password":{"path":"/s/x","issued-by":"outside"}`, "only ends a family"},
|
|
"a name that is also a member": {`"smb-password-*":{"path":"/s/p-*","issued-by":"outside"},
|
|
"smb-password-games":{"path":"/s/games","issued-by":"outside"}`, "also a member"},
|
|
} {
|
|
if _, err := familyManifest(t, c.own); err == nil || !strings.Contains(err.Error(), c.says) {
|
|
t.Errorf("%s: %v; want a refusal saying %q", name, err, c.says)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestAMemberIsFoundByItsFullNameAndLandsWhereTheStarIs(t *testing.T) {
|
|
o := OwnSecrets{
|
|
"smb-password-*": {Path: "/s/smb-password-*.secret", IssuedBy: IssuedOutside},
|
|
"smb-password-big-*": {Path: "/big/*.secret", IssuedBy: IssuedOutside},
|
|
"token": {Path: "/s/token", IssuedBy: IssuedOutside},
|
|
}
|
|
s, family, ok := o.Lookup("smb-password-games")
|
|
if !ok || family != "smb-password-*" || s.Path != "/s/smb-password-games.secret" || s.IssuedBy != IssuedOutside {
|
|
t.Fatalf("a member: %+v %q %v", s, family, ok)
|
|
}
|
|
if s, family, ok := o.Lookup("smb-password-big-one"); !ok || family != "smb-password-big-*" || s.Path != "/big/one.secret" {
|
|
t.Fatalf("the longest family that fits: %+v %q %v", s, family, ok)
|
|
}
|
|
if s, family, ok := o.Lookup("token"); !ok || family != "" || s.Path != "/s/token" {
|
|
t.Fatalf("a secret declared by name: %+v %q %v", s, family, ok)
|
|
}
|
|
for _, name := range []string{"smb-password-*", "smb-password-", "smb-password-../etc", "smb-password-a/b",
|
|
"smb-password-a.b", "smb-password-Games", "smb-password--x", "other"} {
|
|
if _, _, ok := o.Lookup(name); ok {
|
|
t.Errorf("%q was found as a secret", name)
|
|
}
|
|
}
|
|
}
|
|
|
|
// A member given is one file at its path; one not given is nothing, and the machine still composes — the mesh never
|
|
// makes a member.
|
|
func TestAMemberGivenIsPlacedAndOneNotGivenIsNothing(t *testing.T) {
|
|
m, err := familyManifest(t, `"smb-password-*":{"path":"/var/lib/mounts/smb-password-*.secret","issued-by":"outside"}`)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
r := Resolution{Node: "workstation", Modules: []Manifest{m}}
|
|
out, err := r.Declaration(Rendering{Needed: map[string]map[string]string{"mounts": {"smb-password-games": "sealed"}}})
|
|
if err != nil {
|
|
t.Fatalf("a module with one member given did not compose: %v", err)
|
|
}
|
|
var paths []string
|
|
for _, res := range out {
|
|
if res["sealed"] != nil {
|
|
paths = append(paths, res["path"].(string))
|
|
}
|
|
}
|
|
if len(paths) != 1 || paths[0] != "/var/lib/mounts/smb-password-games.secret" {
|
|
t.Fatalf("placed: %v", paths)
|
|
}
|
|
out, err = r.Declaration(Rendering{})
|
|
if err != nil {
|
|
t.Fatalf("a module with no member given did not compose: %v", err)
|
|
}
|
|
for _, res := range out {
|
|
if res["sealed"] != nil {
|
|
t.Fatalf("a member nobody gave was placed: %v", res)
|
|
}
|
|
}
|
|
}
|