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:
+48
-15
@@ -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.
|
||||
|
||||
@@ -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 != "" {
|
||||
|
||||
Reference in New Issue
Block a user