Files
mesh-controller/internal/catalogue/handed_secrets_test.go
T
mesh-tools 97a2d841c7 Land feature 57's controller work on its release branch
The controller plans config, templates and moved providers' provisions,
hands bus accounts to the vault and judges the new manifest words, on the
feature's SDK and node-engine validator (feature 57, config and provisioning).
2026-10-15 21:27:33 +00:00

253 lines
12 KiB
Go

package catalogue
import (
"reflect"
"slices"
"strings"
"testing"
)
// A moved module's own secrets come from the vault (novox/hq ADR 0349 clauses 7 and 11, design 57 V4, issue 647):
// where the module is handed, its declaration carries no sealed value — each file holding a secret alone is placed
// without one and filled by the client, and the plan names every secret its templates and those files need, by
// name. Where it is not handed, and for every module not moved, the composition is byte for byte what it was.
const (
handedBot = `{"module":"bot","version":"1",
"own-secrets":{"token":"${dir:state}/token"},
"build":{"artifacts":[{"name":"files","kind":"archive","from":"files"}]},
"resources":[
{"id":"state","type":"directory","mode":"0700","place":"."},
{"id":"conf","type":"file","path":"/etc/bot/bot.conf","mode":"0640","template":"files/bot.conf"},
{"id":"daemon","type":"process","name":"bot","run":["bot"],"env":{"BOT_TOKEN_FILE":"${dir:state}/token"}}]}`
botText = "token = ${secret:token}\nhome = ${machine:account-home}\n"
)
func botMoved() string {
return strings.Replace(handedBot, `"version":"1",`, `"version":"1","moved":true,`, 1)
}
func builtBot(text string) []Built {
return []Built{{Name: "files", Kind: ArtifactArchive,
Reference: ArtifactStoreScheme + "bot/files/blobs/sha256:" + strings.Repeat("e", 64),
Digest: "sha256:" + strings.Repeat("e", 64), Texts: map[string]string{"bot.conf": text}}}
}
// What the controller seals for the bot when it fills it the old way: a stand-in sealed value.
func sealedForBot(with Rendering) Rendering {
with.Needed = map[string]map[string]string{"bot": {"token": "sealed-to-the-node"}}
return with
}
func TestAHandedModulesSecretsAreNamedAndNoValueIsSealed(t *testing.T) {
r := notifierOn(t, held(t, botMoved(), builtBot(botText)))
c, got := composeWith(t, r, handingTo("store:5000"))
if strings.Contains(got, "sealed") || strings.Contains(got, "${secret:") {
t.Fatalf("a handed module's declaration carries a sealed value or a secret placeholder: %s", got)
}
var token map[string]any
for _, res := range c.Resources {
if res["id"] == "bot.needs-token" {
token = res
}
}
if token == nil {
t.Fatalf("the token's file is not declared: %s", got)
}
if token["path"] == nil || strings.Contains(token["path"].(string), "${") || token["mode"] != "0600" ||
token["owner-at-runtime"] != "owned" || token[HandedSecretField] != nil {
t.Fatalf("the token's file is declared %v: placed, 0600, owned at runtime, no value", token)
}
path := token["path"].(string)
if !reflect.DeepEqual(c.Filled, []string{path, "/etc/bot/bot.conf"}) {
t.Fatalf("the filled files are %v", c.Filled)
}
if want := []PlanSecret{{Name: "token", Path: path}}; !reflect.DeepEqual(c.Plans["bot"].Secrets, want) {
t.Fatalf("the plan names secrets %+v, want %+v", c.Plans["bot"].Secrets, want)
}
// A process naming the token's file still restarts when its client writes it.
for _, res := range c.Resources {
if res["id"] == "bot.daemon" && !strings.Contains(strings.Join(stringsIn(res["restart-on"]), " "), "bot.needs-token") {
t.Errorf("the daemon does not restart on its token's file: %v", res["restart-on"])
}
}
}
// A secret only a template names is in the plan by name alone.
func TestASecretOnlyATemplateNamesIsPlannedByName(t *testing.T) {
raw := strings.Replace(botMoved(), `"own-secrets":{"token":"${dir:state}/token"},`,
`"own-secrets":{"token":"${dir:state}/token","api-key":"${dir:state}/api-key"},`, 1)
r := notifierOn(t, held(t, raw, builtBot(botText+"key = ${secret:api-key}\n")))
c, _ := composeWith(t, r, handingTo("store:5000"))
names := []string{}
for _, s := range c.Plans["bot"].Secrets {
names = append(names, s.Name)
}
if strings.Join(names, " ") != "api-key token" {
t.Fatalf("the plan names %v", c.Plans["bot"].Secrets)
}
}
// Nothing changes for a module that has not moved, nor for a moved one where it is not handed: its secrets are
// sealed into the declaration as before.
func TestAMovedModulesSecretsAreSealedAsBeforeWhereItIsNotHanded(t *testing.T) {
_, before := composeWith(t, notifierOn(t, held(t, handedBot, builtBot(botText))),
sealedForBot(handingTo("store:5000")))
if !strings.Contains(before, "sealed-to-the-node") {
t.Fatalf("the unmoved module is not sealed the old way: %s", before)
}
for name, with := range map[string]Rendering{
"an engine that does not take a filled file alone": {Provisioner: "mesh-provisioner-client", ArtifactStore: "store:5000"},
"no provisioner client on the machine": {Hands: true, ArtifactStore: "store:5000"},
} {
c, got := composeWith(t, notifierOn(t, held(t, botMoved(), builtBot(botText))), sealedForBot(with))
if got != before {
t.Errorf("%s: the moved module composes differently from its unmarked twin:\nbefore: %s\nnow: %s",
name, before, got)
}
if len(c.Filled) > 0 || c.Plans != nil {
t.Errorf("%s: something was handed: %v %v", name, c.Filled, c.Plans)
}
}
}
func TestWhatAHandedModuleCannotHaveFromTheVaultIsRefused(t *testing.T) {
// A template naming a credential the module requires: a binding's, which moves with its provider.
requires := strings.Replace(botMoved(), `"own-secrets":`, `"requires":["database"],"secrets":{"database":"/etc/bot/db"},"own-secrets":`, 1)
r, err := Resolve(shelf(held(t, requires, builtBot("db = ${secret:database}\n")),
Manifest{Module: "db", Version: "1", Provides: []Offer{{Name: "database", Scope: ScopeNode}}}),
[]string{"bot"}, workstation(), World{})
if err != nil {
t.Fatal(err)
}
r.Account = "op"
if _, err := r.Compose(handingTo("store:5000")); err == nil || !strings.Contains(err.Error(), "moves with its provider") {
t.Errorf("a template naming a required credential: %v", err)
}
// A file with its text inline naming a secret, in a handed module.
inline := strings.Replace(botMoved(), `{"id":"daemon"`,
`{"id":"legacy","type":"file","path":"/etc/bot/legacy","content":"t = ${secret:token}\n"},{"id":"daemon"`, 1)
r = notifierOn(t, held(t, inline, builtBot(botText)))
if _, err := r.Compose(handingTo("store:5000")); err == nil || !strings.Contains(err.Error(), "keep it in a template") {
t.Errorf("an inline file naming a secret in a handed module: %v", err)
}
// A family and a secret issued outside the mesh are not handed by name yet: the mark is refused.
for why, raw := range map[string]string{
"a family": strings.Replace(botMoved(), `"own-secrets":{"token":"${dir:state}/token"}`, `"own-secrets":{"token":"${dir:state}/token","key-*":"${dir:state}/keys"}`, 1),
"issued outside": strings.Replace(botMoved(), `"own-secrets":{"token":"${dir:state}/token"}`, `"own-secrets":{"token":{"path":"${dir:state}/token","issued-by":"outside"}}`, 1),
} {
if _, err := ParseManifest([]byte(raw)); err == nil || !strings.Contains(err.Error(), "design 57 V4") {
t.Errorf("a moved module with %s: %v", why, err)
}
}
}
// A credential of what a handed module requires that the vault keeps for it alone (issue 757): the forge's read
// token for one agent, issued by the forge's software per holder. Where the module is handed, its file is declared
// placed and without a value, and the plan names it with its provision and provider; anywhere else, and for a
// credential the mesh makes, the composition is what it was.
func forgeReader(t *testing.T, moved bool, issued bool) Resolution {
t.Helper()
raw := strings.Replace(handedBot, `"own-secrets":`,
`"requires":[{"name":"forge-read","optional":true}],"secrets":{"forge-read":"${dir:state}/forge.secret"},"own-secrets":`, 1)
if moved {
raw = strings.Replace(raw, `"version":"1",`, `"version":"1","moved":true,`, 1)
}
r := notifierOn(t, held(t, raw, builtBot(botText)))
r.Needs = append(r.Needs, Needed{Name: "forge-read", From: "novox", For: "bot", Module: "forge",
Sealed: "sealed-forge-read", IssuedToHolder: issued})
return r
}
func TestAHandedModulesCredentialIssuedForItIsNamedInItsPlanAndNoValueIsSealed(t *testing.T) {
c, got := composeWith(t, forgeReader(t, true, true), handingTo("store:5000"))
if strings.Contains(got, "sealed") || strings.Contains(got, HandedProvisionField) {
t.Fatalf("a handed module's declaration carries a sealed value or the marker: %s", got)
}
var file map[string]any
for _, res := range c.Resources {
if res["id"] == "bot."+SecretID("forge-read") {
file = res
}
}
if file == nil {
t.Fatalf("the credential's file is not declared: %s", got)
}
path, _ := file["path"].(string)
if path == "" || strings.Contains(path, "${") || file["mode"] != "0600" || file["owner-at-runtime"] != "owned" {
t.Fatalf("the credential's file is declared %v: placed, 0600, owned at runtime, no value", file)
}
if !strings.Contains(strings.Join(c.Filled, " "), path) {
t.Fatalf("the credential's file is not among the filled files %v", c.Filled)
}
want := []PlanProvision{{Name: "forge-read", Provision: "forge-read", Provider: "novox", ProviderModule: "forge",
Path: path, From: "novox", As: ConsumerIdentity("workstation", "bot")}}
if !reflect.DeepEqual(c.Plans["bot"].Provisions, want) {
t.Fatalf("the plan names %+v, want %+v", c.Plans["bot"].Provisions, want)
}
}
func TestACredentialIssuedForAHolderIsSealedAsBeforeWhereItsModuleIsNotHanded(t *testing.T) {
_, before := composeWith(t, forgeReader(t, false, false), sealedForBot(handingTo("store:5000")))
if !strings.Contains(before, "sealed-forge-read") {
t.Fatalf("the unmoved module's credential is not sealed the old way: %s", before)
}
for why, r := range map[string]Resolution{
"a module not moved": forgeReader(t, false, true),
"a moved module whose credential is made": forgeReader(t, true, false),
} {
c, got := composeWith(t, r, sealedForBot(handingTo("store:5000")))
if !strings.Contains(got, "sealed-forge-read") {
t.Errorf("%s: the credential is not sealed the old way: %s", why, got)
}
// Its provider is named (issue 760), and no credential for the client to fill.
if p, ok := c.Plans["bot"]; ok && slices.ContainsFunc(p.Provisions, func(pr PlanProvision) bool {
return pr.Name != "" || pr.Path != ""
}) {
t.Errorf("%s: the plan names a credential to fill: %+v", why, p.Provisions)
}
}
c, got := composeWith(t, forgeReader(t, true, true), sealedForBot(Rendering{Provisioner: "mesh-provisioner-client",
ArtifactStore: "store:5000"}))
if !strings.Contains(got, "sealed-forge-read") || c.Plans != nil {
t.Errorf("where the engine does not take a filled file alone, the credential is not sealed the old way: %s", got)
}
}
// A handed consumer's own login from a provider that gives each consumer one (issue 779): the same entry as for an
// issued credential, with the provider's form on it, and nothing sealed; where the consumer is not handed, the
// login is sealed as it was.
func TestAHandedConsumersOwnLoginFromAPerLoginProviderIsNamedInItsPlanAndNoValueIsSealed(t *testing.T) {
pair := func(handed bool) Resolution {
r := forgeReader(t, true, handed)
r.Needs[len(r.Needs)-1].CredentialForm = CredentialFormPair
return r
}
c, got := composeWith(t, pair(true), handingTo("store:5000"))
if strings.Contains(got, "sealed") || strings.Contains(got, HandedProvisionField) {
t.Fatalf("a handed consumer's declaration carries its login sealed or the marker: %s", got)
}
var path string
for _, res := range c.Resources {
if res["id"] == "bot."+SecretID("forge-read") {
path, _ = res["path"].(string)
if res["mode"] != "0600" || res["owner-at-runtime"] != "owned" || res["content"] != nil || res["sealed"] != nil {
t.Fatalf("the login's file is declared %v: placed, 0600, owned at runtime, no value", res)
}
}
}
if path == "" || !strings.Contains(strings.Join(c.Filled, " "), path) {
t.Fatalf("the login's file %q is not declared and filled: %v", path, c.Filled)
}
want := []PlanProvision{{Name: "forge-read", Provision: "forge-read", Provider: "novox", ProviderModule: "forge",
CredentialForm: CredentialFormPair, Path: path, From: "novox", As: ConsumerIdentity("workstation", "bot")}}
if !reflect.DeepEqual(c.Plans["bot"].Provisions, want) {
t.Fatalf("the plan names %+v, want %+v", c.Plans["bot"].Provisions, want)
}
// Not marked as the vault's, the login is sealed as before.
_, before := composeWith(t, pair(false), sealedForBot(handingTo("store:5000")))
if !strings.Contains(before, "sealed-forge-read") {
t.Fatalf("a consumer whose login the vault does not keep for it has it sealed no more: %s", before)
}
}