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
|
// 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
|
// as an own secret `broker-<binary>`, at `${dir:<id>}/broker-<binary>` for a directory resource every parent of which the
|
||||||
// (mode with its last digit odd, such as 0711 or 0755); the file itself is 0600 and the runtime account's.
|
// runtime's account may pass (passableDir); the file itself is 0600 and the runtime account's.
|
||||||
func toolsSecretProblems(m Manifest) []string {
|
func toolsSecretProblems(m Manifest) []string {
|
||||||
var problems []string
|
var problems []string
|
||||||
for _, a := range m.BusAccounts() {
|
for _, a := range m.BusAccounts() {
|
||||||
@@ -113,24 +113,57 @@ func toolsSecretProblems(m Manifest) []string {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
dir, _, _ := strings.Cut(strings.TrimPrefix(own.Path, "${dir:"), "}")
|
dir, _, _ := strings.Cut(strings.TrimPrefix(own.Path, "${dir:"), "}")
|
||||||
passable := false
|
if !strings.HasPrefix(own.Path, "${dir:"+dir+"}/") || !passableDir(m, dir, 0) {
|
||||||
for _, r := range m.Resources {
|
problems = append(problems, fmt.Sprintf("%s's own secret %s is at %s, which the node's tool runtime cannot "+
|
||||||
if fmt.Sprint(r["type"]) != "directory" || fmt.Sprint(r["id"]) != dir || !strings.HasPrefix(own.Path, "${dir:"+dir+"}/") {
|
"reach: a tools bundle's account lies in a directory resource the runtime's account may pass, every parent "+
|
||||||
continue
|
"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 "+
|
||||||
mode, _ := r["mode"].(string)
|
"own (novox/hq ADR 0564 Limits 4)", m.Module, name, own.Path, meshStateRoot, m.Module, name))
|
||||||
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))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return problems
|
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
|
// 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
|
// 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.
|
// 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
|
// 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
|
// 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.
|
// 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",` +
|
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",` +
|
`"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 +
|
`"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 + `}]}}`
|
`"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"}]`
|
broker := `[{"name":"mesh-broker","scope":"mesh"}]`
|
||||||
for _, c := range []struct {
|
for _, c := range []struct {
|
||||||
name, manifest, refused string
|
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 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"},
|
{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).
|
// 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"),
|
{name: "a tools secret the runtime cannot reach", manifest: toolsIn(broker, `{"removes":["mesh-host_acts"]}`, "0700",
|
||||||
refused: "others may pass through"},
|
"/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))
|
m, err := ParseManifest([]byte(c.manifest))
|
||||||
if c.refused != "" {
|
if c.refused != "" {
|
||||||
|
|||||||
Reference in New Issue
Block a user