Own directories of a joining module carry setgid, to an engine that takes it (hq ADR 0334 v4)

This commit is contained in:
2026-10-13 00:31:54 +02:00
parent 96e15f8693
commit 1de9bbc448
4 changed files with 29 additions and 11 deletions
+3 -3
View File
@@ -262,8 +262,8 @@ type Rendering struct {
TakenLibraries map[string]bool
LibraryPaths map[string][]string
// RunnerJoinsOwnGroups says this machine's engine puts the tool runner's account in a joining module's own group
// (novox/hq ADR 0334 §2: its statement's contract is link.OwnGroupContract or later); to an older one the
// identity's runner is not sent.
// and takes a directory's setgid mode (novox/hq ADR 0334 §1, §2: its statement's contract is link.OwnGroupContract
// or later); to an older one neither the identity's runner nor the setgid bit is sent.
RunnerJoinsOwnGroups bool
}
@@ -1062,7 +1062,7 @@ func (r Resolution) compose(with Rendering, owner map[string]string,
// And its identity and libraries, where it joins (novox/hq ADR 0328 §4, §5, §10): after the placement's
// owner, which a joining module may not set.
if joins {
if err := joinedInto(copied, m, joined, libPaths); err != nil {
if err := joinedInto(copied, m, joined, libPaths, with.RunnerJoinsOwnGroups); err != nil {
return nil, err
}
}
+13 -5
View File
@@ -424,8 +424,8 @@ func toolsDirectoryProblems(m Manifest) []string {
}
if n&0o050 != 0o050 {
problems = append(problems, fmt.Sprintf("%s's tools name its directory %s, whose mode %s closes it to its "+
"own group: the tool runner reads it through that group, so it needs group read and enter (0750) "+
"(novox/hq ADR 0334 §3)", m.Module, id, mode))
"own group: the tool runner reads it through that group, so it needs group read and enter (0750; "+
"the mesh adds setgid, 2750) (novox/hq ADR 0334 §3)", m.Module, id, mode))
}
}
}
@@ -619,7 +619,7 @@ func identityFill(s string, j Joined) string {
// joinedInto composes one resource of a joining module (§4, §5, §10): ${identity:…} filled everywhere, a container's
// ${library:<id>} volume sources filled with the host path and a read entry mounted read-only, and a directory owned by
// the module's user and primary group.
func joinedInto(resource map[string]any, m Manifest, j Joined, paths map[string]string) error {
func joinedInto(resource map[string]any, m Manifest, j Joined, paths map[string]string, setgid bool) error {
for k, v := range resource {
if k == "id" || k == "type" {
continue
@@ -630,10 +630,18 @@ func joinedInto(resource map[string]any, m Manifest, j Joined, paths map[string]
}
resource[k] = filled
}
// Its own directories belong to its user and its own group (ADR 0334 §1): the library's group is its processes'
// primary, so what they make in a library keeps that group, and its own data stays its own.
// Its own directories belong to its user and its own group, with setgid (ADR 0334 §1, v4): the library's group is
// its processes' primary, so what they make in a library keeps that group — and what they make in their own
// directories keeps mesh-<module> by the setgid bit. The bit only to an engine that takes a mode with it
// (Rendering.RunnerJoinsOwnGroups, link.OwnGroupContract): the one pinned refuses a mode not starting with 0.
if fmt.Sprint(resource["type"]) == "directory" {
resource["owner"] = fmt.Sprintf("%d:%d", j.UID, j.UID)
if mode, ok := resource["mode"].(string); ok && setgid {
var n uint64
if _, err := fmt.Sscanf(mode, "%o", &n); err == nil && mode != "" {
resource["mode"] = fmt.Sprintf("%04o", n|0o2000)
}
}
}
if fmt.Sprint(resource["type"]) != "container" {
return nil
+10 -1
View File
@@ -357,6 +357,11 @@ func TestAJoiningModulesOwnDirectoriesAreItsOwnGroups(t *testing.T) {
if owner := got["radarr.config"]["owner"]; owner != "50020:50020" {
t.Errorf("its directory is owned by %v; want its user and its own group", owner)
}
// ADR 0334 v4: with setgid, so what is made in it later keeps mesh-radarr though the process's primary group is the
// library's.
if mode := got["radarr.config"]["mode"]; mode != "0750" {
t.Errorf("an engine that refuses setgid modes was sent %v", mode)
}
if env := got["radarr.app"]["env"].(map[string]any); env["PGID"] != "50010" {
t.Errorf("its process's primary group is %v; want the library's, so what it makes there keeps the group", env["PGID"])
}
@@ -366,9 +371,13 @@ func TestAJoiningModulesOwnDirectoriesAreItsOwnGroups(t *testing.T) {
}
with := librariesRendering()
with.RunnerJoinsOwnGroups = true
if got := composeLibraries(t, r, with).Identities[0].Runner; got != "jochen" {
newer := composeLibraries(t, r, with)
if got := newer.Identities[0].Runner; got != "jochen" {
t.Errorf("the runner joins %q's own group; want jochen, whose tools it runs", got)
}
if mode := byID(newer.Resources)["radarr.config"]["mode"]; mode != "2750" {
t.Errorf("its directory's mode is %v; want 2750, setgid on its own 0750", mode)
}
}
// novox/hq ADR 0334 §3: a joining module whose tools name a directory whose mode closes it to its group is refused at
+3 -2
View File
@@ -452,8 +452,9 @@ const IdentityContract = 5
const LibraryContract = 6
// OwnGroupContract is the statement of an engine that also puts the tool runner's account in a joining module's own
// group, named by an identity's `runner` (novox/hq ADR 0334 §2). Not stated by any engine yet: the engine must add
// `runner` (an account's name) to its declaration's Identity, and put that account in the group mesh-<module>.
// group, named by an identity's `runner`, and takes a directory's mode with the setgid bit, such as "2750" (novox/hq
// ADR 0334 §1, §2). Not stated by any engine yet: the engine must add `runner` (an account's name) to its declaration's
// Identity and put that account in the group mesh-<module>, and accept a directory mode whose first digit is 2.
const OwnGroupContract = 7
// KindLibrary is a library's own verdict on its home, as the engine may state it (novox/hq ADR 0328): healthy once