Pin the node-engine at its reviewed grant, and hold the mount match to the path itself (hq ADR 0312, issue 537 review)

A path beginning with the same letters is another path, and a read-only mount
of a read-write access is still granted: the grant follows what mounts it.
This commit is contained in:
2026-10-12 13:23:22 +02:00
parent 704ead6f09
commit 47037b1a91
6 changed files with 38 additions and 9 deletions
+1 -1
View File
@@ -35,4 +35,4 @@ require (
// committed. Every build (the build agent's `go build`, the Dockerfile) compiles from vendor/ and
// fetches nothing; go refuses to build when vendor/ and this file disagree, so a pin moved without
// `go mod vendor` fails loudly, at once, everywhere.
replace github.com/novox/mesh-host => git.novox.be/novox/mesh-host v0.0.0-20261012102338-f4efc3cad0a6
replace github.com/novox/mesh-host => git.novox.be/novox/mesh-host v0.0.0-20261012105342-c433e76de229
+2 -2
View File
@@ -1,5 +1,5 @@
git.novox.be/novox/mesh-host v0.0.0-20261012102338-f4efc3cad0a6 h1:2AvHCfs6dKOAOBTUoxkebEb5ZxHjQbAeeIMdgRtap6A=
git.novox.be/novox/mesh-host v0.0.0-20261012102338-f4efc3cad0a6/go.mod h1:tcTK4LMs1d6JpZUwy3hSHMFG/MIuDr/XFs7/nbeaW/c=
git.novox.be/novox/mesh-host v0.0.0-20261012105342-c433e76de229 h1:xakx5tpeQBQhzLMd5Besg3h5OY8l752oExn+fs1xocg=
git.novox.be/novox/mesh-host v0.0.0-20261012105342-c433e76de229/go.mod h1:tcTK4LMs1d6JpZUwy3hSHMFG/MIuDr/XFs7/nbeaW/c=
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261010191001-33917f91ac3c h1:l5onJwoIeH8yE/PjVlEeoPyXBsHp2NQiWLllz2fwX7s=
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261010191001-33917f91ac3c/go.mod h1:GFuZUElBZ9A++mxgIKo97aXXo+kV0uJ/UkbhQPPIbrY=
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op h1:Z/MZK75wC/NSrkgqeNIa7jexam9uWzhLmFTSCPI/kn0=
+4 -1
View File
@@ -89,7 +89,10 @@ func grantAccesses(out []map[string]any, owner map[string]string, module, accoun
return toAccount
}
// mounts says whether a container mounts path, or something below it, from the machine.
// mounts says whether a container mounts path, or something below it, from the machine. A path that merely
// begins with the same letters — /storage/media2 beside /storage/media — is another path. A read-only mount of a
// read-write access still counts: the module declared the access read-write, the engine grants the group to what
// mounts it, and a read-only mount only means that container chose not to write through it.
func mounts(container map[string]any, path string) bool {
volumes, _ := container["volumes"].([]any)
path = filepath.Clean(path)
+20
View File
@@ -152,3 +152,23 @@ func keysOf(m map[string]map[string]any) []string {
}
return out
}
// Only a container that mounts the access's path, or below it, is named: a path that begins with the same letters is
// another path. A read-only mount of a read-write access is still named — the access is read-write, and the grant
// follows what mounts it, not how.
func TestOnlyAContainerMountingThePathIsNamedReadOnlyIncluded(t *testing.T) {
m := mod("library", nil, nil, nil)
m.Accesses = []Access{{ID: "media", Path: "/storage/media", Mode: AccessReadWrite}}
m.Resources = []map[string]any{
{"id": "beside", "type": "container", "name": "beside", "image": anImage, "volumes": []any{"/storage/media2:/x"}},
{"id": "reader", "type": "container", "name": "reader", "image": anImage, "volumes": []any{"${access:media}:/media:ro"}},
}
out, err := Resolution{Node: "ace", Modules: []Manifest{m}}.Declaration(Rendering{GrantsGroups: true})
if err != nil {
t.Fatal(err)
}
grant, _ := byID(out)["library."+AccessID("/storage/media")][GrantField].(map[string]any)
if fmt.Sprint(grant["containers"]) != "[library.reader]" {
t.Errorf("the access grants %v; want the read-only mount and not /storage/media2", grant)
}
}
+9 -3
View File
@@ -522,9 +522,11 @@ func (a *Access) validate(where string, _ bool) []string {
problems = append(problems, where+": a grant names a container with no id")
}
}
if a.Grant.Account != "" && strings.ContainsAny(a.Grant.Account, " :/\t\n") {
problems = append(problems, where+": a grant's account is an account's name, and "+
a.Grant.Account+" is not one")
if a.Grant.Account != "" && !accountName.MatchString(a.Grant.Account) {
// Handed to id, usermod and gpasswd as an argument: a name starting with a dash would be read as an
// option by each of them.
problems = append(problems, fmt.Sprintf("%s: a grant's account is an account's name — letters, digits, "+
"dots, underscores and dashes, not starting with a dash — and %q is not one", where, a.Grant.Account))
}
}
return problems
@@ -1778,6 +1780,10 @@ func checkWhileStopped(resources []Resource) []string {
return problems
}
// accountName is what a grant's account may be: the portable account name the user tools take, optionally ending
// in `$`, never starting with a dash.
var accountName = regexp.MustCompile(`^[A-Za-z0-9_.][A-Za-z0-9_.-]*\$?$`)
// checkGrants judges each access's grant against the whole declaration (novox/hq ADR 0312): a container it
// names is a container of this declaration, as a maintenance window's are. A grant to a container the machine
// is not told about would be a group given to nothing, said as granted.
+2 -2
View File
@@ -78,7 +78,7 @@ github.com/nats-io/nkeys
# github.com/nats-io/nuid v1.0.1
## explicit
github.com/nats-io/nuid
# github.com/novox/mesh-host v0.0.0 => git.novox.be/novox/mesh-host v0.0.0-20261012102338-f4efc3cad0a6
# github.com/novox/mesh-host v0.0.0 => git.novox.be/novox/mesh-host v0.0.0-20261012105342-c433e76de229
## explicit; go 1.26.0
github.com/novox/mesh-host/internal/declaration
github.com/novox/mesh-host/rootsearch
@@ -135,4 +135,4 @@ golang.org/x/text/width
# golang.org/x/time v0.15.0
## explicit; go 1.25.0
golang.org/x/time/rate
# github.com/novox/mesh-host => git.novox.be/novox/mesh-host v0.0.0-20261012102338-f4efc3cad0a6
# github.com/novox/mesh-host => git.novox.be/novox/mesh-host v0.0.0-20261012105342-c433e76de229