Refuse a tools account secret under a parent the runtime cannot pass, so the rule covers the module's 0700 mesh place too (issue 1952 S5, ADR 0564 Limits 4)

This commit is contained in:
2026-10-29 22:00:43 +01:00
parent 8e3d7a4c3c
commit 32f5ce3712
2 changed files with 56 additions and 20 deletions
+48 -15
View File
@@ -97,8 +97,8 @@ func ToolsBusSecret(m Manifest, name string) bool {
}
// toolsSecretProblems refuses a tools bundle's bus account secret the runtime's account could not read: it is declared
// as an own secret `broker-<binary>`, at a path in a directory resource of the module that others may pass through
// (mode with its last digit odd, such as 0711 or 0755); the file itself is 0600 and the runtime account's.
// as an own secret `broker-<binary>`, at `${dir:<id>}/broker-<binary>` for a directory resource every parent of which the
// runtime's account may pass (passableDir); the file itself is 0600 and the runtime account's.
func toolsSecretProblems(m Manifest) []string {
var problems []string
for _, a := range m.BusAccounts() {
@@ -113,24 +113,57 @@ func toolsSecretProblems(m Manifest) []string {
continue
}
dir, _, _ := strings.Cut(strings.TrimPrefix(own.Path, "${dir:"), "}")
passable := false
for _, r := range m.Resources {
if fmt.Sprint(r["type"]) != "directory" || fmt.Sprint(r["id"]) != dir || !strings.HasPrefix(own.Path, "${dir:"+dir+"}/") {
continue
}
mode, _ := r["mode"].(string)
passable = mode != "" && strings.ContainsAny(mode[len(mode)-1:], "1357")
}
if !passable {
problems = append(problems, fmt.Sprintf("%s's own secret %s is at %s; a tools bundle's account is read by the "+
"node's tool runtime, so it lies in a directory resource of the module that others may pass through "+
"(mode 0711 or 0755, `${dir:<id>}/%s`), the file itself the runtime account's, 0600 (novox/hq ADR 0564 "+
"Limits 4)", m.Module, name, own.Path, name))
if !strings.HasPrefix(own.Path, "${dir:"+dir+"}/") || !passableDir(m, dir, 0) {
problems = append(problems, fmt.Sprintf("%s's own secret %s is at %s, which the node's tool runtime cannot "+
"reach: a tools bundle's account lies in a directory resource the runtime's account may pass, every parent "+
"included, such as {\"id\":\"tools-secret\",\"type\":\"directory\",\"path\":\"%s/%s-tools\",\"mode\":\"0711\"} "+
"with the secret at ${dir:tools-secret}/%s; never under the module's own or mesh place, which are its "+
"own (novox/hq ADR 0564 Limits 4)", m.Module, name, own.Path, meshStateRoot, m.Module, name))
}
}
return problems
}
// meshStateRoot is the mesh's directory of every module's mesh place on a node of the default data root: root's, mode
// 0755, so the runtime's account passes it, while each module's own place beneath it is 0700.
const meshStateRoot = defaultDataRoot + "/" + placeMesh
// passableDir says the runtime's account may pass the directory resource id and every parent the mesh makes for it: its
// own mode lets others pass (the last digit odd), and it is either stated directly under meshStateRoot or beneath a
// placed directory that is passable in turn. A pathless directory resolves under the module's own root or its mesh
// place, which are the module's own, so it never is.
func passableDir(m Manifest, id string, depth int) bool {
if depth > 4 {
return false
}
for _, r := range m.Resources {
if fmt.Sprint(r["type"]) != "directory" || fmt.Sprint(r["id"]) != id {
continue
}
mode, _ := r["mode"].(string)
if mode == "" || !strings.ContainsAny(mode[len(mode)-1:], "1357") {
return false
}
path, _ := r["path"].(string)
switch {
case strings.HasPrefix(path, "${dir:"):
parent, _, _ := strings.Cut(strings.TrimPrefix(path, "${dir:"), "}")
return passableDir(m, parent, depth+1)
case strings.HasPrefix(path, "/"):
return filepathDir(strings.TrimRight(path, "/")) == meshStateRoot
}
return false
}
return false
}
func filepathDir(p string) string {
if i := strings.LastIndex(p, "/"); i > 0 {
return p[:i]
}
return "/"
}
// toolsBusProblems is what is wrong with a tools bundle's bus (ADR 0564 Limits 4): a bus on anything but a bundle
// compiled to a binary the runtime loads, a removal outside the closed set, or a removal beside any other grant or by a
// module that does not hold mesh-broker. Every other field is judged as a process's.
+8 -5
View File
@@ -51,14 +51,14 @@ func TestAProcessDeclaresItsBusAndIsAccepted(t *testing.T) {
// 1952 S5 (novox/hq ADR 0564 Limits 4; the lead, 2026-10-29): a tools bundle declares its own bus account, read as a
// process's, named after its binary and kept through resolution; a removal is from the closed set, alone, and the
// bus module's (mesh-broker) alone.
toolsIn := func(claims, bus, mode string) string {
toolsIn := func(claims, bus, mode, where string) string {
return `{"module":"nats","version":"1","claims":` + claims + `,"own-secrets":{"broker":"${dir:mesh-state}/broker",` +
`"broker-nats-tools":"${dir:tools-secret}/broker-nats-tools"},"resources":[{"id":"mesh-state","type":"directory",` +
`"mode":"0700","place":"mesh","unknown":"program"},{"id":"tools-secret","type":"directory","mode":"` + mode +
`","place":"mesh","unknown":"program"}],"build":{"artifacts":[{"name":"tools","kind":"bundle",` +
`","path":"` + where + `","unknown":"program"}],"build":{"artifacts":[{"name":"tools","kind":"bundle",` +
`"language":"go","system":"arch","from":"cmd/nats-tools","binary":"nats-tools","loads":["nats-tools"],"bus":` + bus + `}]}}`
}
tools := func(claims, bus string) string { return toolsIn(claims, bus, "0711") }
tools := func(claims, bus string) string { return toolsIn(claims, bus, "0711", "/var/lib/mesh/nats-tools") }
broker := `[{"name":"mesh-broker","scope":"mesh"}]`
for _, c := range []struct {
name, manifest, refused string
@@ -70,8 +70,11 @@ func TestAProcessDeclaresItsBusAndIsAccepted(t *testing.T) {
{name: "a tools bundle consuming a queue", manifest: tools(broker, `{"queues":["x"]}`), refused: "holds no consumer"},
{name: "a process removing", manifest: mailSenderProcessManifest(t, `{"removes":["mesh-host_acts"]}`), refused: "only a tools bundle"},
// The runtime's account reads the account's secret: it lies where others may pass (the host agent, 2026-10-29).
{name: "a tools secret the runtime cannot reach", manifest: toolsIn(broker, `{"removes":["mesh-host_acts"]}`, "0700"),
refused: "others may pass through"},
{name: "a tools secret the runtime cannot reach", manifest: toolsIn(broker, `{"removes":["mesh-host_acts"]}`, "0700",
"/var/lib/mesh/nats-tools"), refused: "cannot reach"},
// Its parents too: under the module's mesh place, /var/lib/mesh/nats is 0700 (the lead, read on novox 2026-10-29).
{name: "a tools secret beneath the module's mesh place", manifest: toolsIn(broker, `{"removes":["mesh-host_acts"]}`, "0711",
"${dir:mesh-state}/tools"), refused: "cannot reach"},
} {
m, err := ParseManifest([]byte(c.manifest))
if c.refused != "" {