From 30599c185855b4f8d4cf486dd0287347299162fa Mon Sep 17 00:00:00 2001 From: jochen Date: Mon, 12 Oct 2026 13:09:05 +0200 Subject: [PATCH] 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. --- go.mod | 2 +- go.sum | 4 ++-- internal/catalogue/grants.go | 5 ++++- internal/catalogue/grants_test.go | 20 +++++++++++++++++++ .../internal/declaration/declaration.go | 12 ++++++++--- vendor/modules.txt | 4 ++-- 6 files changed, 38 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 64c2a70c..b2fd83ca 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index d2d2ed69..9e8b699c 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/internal/catalogue/grants.go b/internal/catalogue/grants.go index ec20ef15..2574bdb7 100644 --- a/internal/catalogue/grants.go +++ b/internal/catalogue/grants.go @@ -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) diff --git a/internal/catalogue/grants_test.go b/internal/catalogue/grants_test.go index 39d833f0..382b8e68 100644 --- a/internal/catalogue/grants_test.go +++ b/internal/catalogue/grants_test.go @@ -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) + } +} diff --git a/vendor/github.com/novox/mesh-host/internal/declaration/declaration.go b/vendor/github.com/novox/mesh-host/internal/declaration/declaration.go index e4318f9c..e2f6d294 100644 --- a/vendor/github.com/novox/mesh-host/internal/declaration/declaration.go +++ b/vendor/github.com/novox/mesh-host/internal/declaration/declaration.go @@ -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. diff --git a/vendor/modules.txt b/vendor/modules.txt index 56f70d56..775fd133 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -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