package resolution import ( "testing" mcat "git.novox.be/novox/mesh-sdk/go/catalogue" ) // mailSenderManifest is the fixture every mail-sender issue builds on: the real parts of mesh-mail's manifest // taken from mesh-catalog/modules/mesh-mail/module.json, extended as ADR 0436 §7-§8 describe, with `own` naming // the account that owns the family's files. func mailSenderManifest(t *testing.T, own string) mcat.Manifest { t.Helper() ownSecrets := `"mail-credential-*":{"path":"/var/lib/mesh-mail/mail-credential-*.secret","issued-by":"outside"` if own != "" { ownSecrets += `,"owner":"` + own + `"` } ownSecrets += `}` m, err := decodeForTest([]byte(`{"module":"mesh-mail","version":"1","secrets-owner":"mesh-mail","own-secrets":{` + ownSecrets + `},"resources":[{"id":"account","type":"user","name":"mesh-mail-send"}]}`)) if err != nil { t.Fatalf("a manifest with own=%q was refused: %v", own, err) } return m } // 1170/AC1: a family owned by the module's own account lands at 0600, owned by that account — and a module's // other own secrets (no per-secret owner declared) keep secrets-owner, unchanged. func TestAnOwnSecretsOwnerIsGivenToItsFiles(t *testing.T) { m := mailSenderManifest(t, "mesh-mail-send") m.OwnSecrets["smtp-relay-key"] = mcat.OwnSecret{Path: "/var/lib/mesh-mail/smtp-relay-key", Taken: mcat.TakenAtStart} r := mcat.Resolution{Node: "novox", Modules: []mcat.Manifest{m}} out, err := Declaration(r, mcat.Rendering{ Needed: map[string]map[string]string{"mesh-mail": {"mail-credential-smtp": "sealed-credential", "smtp-relay-key": "sealed-relay"}}, }) if err != nil { t.Fatalf("composing the declaration: %v", err) } var family, plain map[string]any for _, res := range out { if res["type"] != "file" { continue } switch res["path"] { case "/var/lib/mesh-mail/mail-credential-smtp.secret": family = res case "/var/lib/mesh-mail/smtp-relay-key": plain = res } } if family == nil { t.Fatalf("the family member was not composed at all: %v", out) } if got := family["owner"]; got != "mesh-mail-send" { t.Fatalf("the family member's owner: %v; want mesh-mail-send", got) } // review round 1, finding 7: asserted, not merely logged. ownedBy never sets "mode", so a sealed secret // file carries none here either — the host applies 0600 to every one regardless (novox/hq ADR 0086) — // and the owner clause must not have added one of its own that could override that. if _, has := family["mode"]; has { t.Fatalf("the family member carries its own mode %v; the owner clause must not set one, so the "+ "host's 0600 for every sealed secret file stands", family["mode"]) } if plain == nil { t.Fatalf("the module's other own secret was not composed at all: %v", out) } if got := plain["owner"]; got != "mesh-mail" { t.Fatalf("a secret with no per-secret owner kept %v; want the module's secrets-owner %q", got, "mesh-mail") } } // 1170/AC3: a manifest with no owner on any own secret composes exactly as it did before this clause existed: // every secret file belongs to the module's secrets-owner (or nothing, when that is absent too). func TestNoOwnerOnAnySecretComposesAsMainDoes(t *testing.T) { m := mailSenderManifest(t, "") r := mcat.Resolution{Node: "novox", Modules: []mcat.Manifest{m}} out, err := Declaration(r, mcat.Rendering{ Needed: map[string]map[string]string{"mesh-mail": {"mail-credential-smtp": "sealed-credential"}}, }) if err != nil { t.Fatalf("composing the declaration: %v", err) } var found bool for _, res := range out { if res["type"] == "file" && res["path"] == "/var/lib/mesh-mail/mail-credential-smtp.secret" { found = true if got := res["owner"]; got != "mesh-mail" { t.Fatalf("with no per-secret owner declared, the file's owner: %v; want secrets-owner %q", got, "mesh-mail") } } } if !found { t.Fatalf("the family member was not composed at all: %v", out) } }