Take a directory in the mesh's own tree that the engine's account owns as the mesh's, not as found (issue 1873)

postgres's data directory moved to /var/lib/mesh/postgres. On ace and novox that
directory was there, root:root 0700, before the directory resource was applied, so
the engine used it as found (issue 339's rule) and left it to root: the postgres
provider, running as the operator's account, cannot reach its own secrets,
consumers and grants there.

/var/lib/mesh is the engine's tree (root, 0755): only the engine's account makes a
directory in it. A directory below it owned by that account is now the mesh's and
is given its declared owner and mode. Anywhere else, or owned by another account,
issue 339's rule stands.
This commit is contained in:
2026-10-22 22:23:04 +02:00
parent 59e6a6fa2a
commit cdbff1777e
3 changed files with 82 additions and 0 deletions
+15
View File
@@ -1482,6 +1482,9 @@ func applyDirectory(r *declaration.Directory, previous store.Applied, wasBefore
} }
if existed && wasBefore { if existed && wasBefore {
ours := previous.Target != "" && filepath.Clean(previous.Target) == filepath.Clean(r.Path) && !previous.AsFound ours := previous.Target != "" && filepath.Clean(previous.Target) == filepath.Clean(r.Path) && !previous.AsFound
// In the mesh's own tree, a directory the engine's own account owns was made by the mesh, on the way to
// something below it or by an earlier apply that did not record it: it is the mesh's (novox/hq issue 1873).
ours = ours || madeInTheMeshsTree(r.Path, before)
if !ours { if !ours {
owned, err := ownedBy(r.Path, r.Owner) owned, err := ownedBy(r.Path, r.Owner)
if err != nil { if err != nil {
@@ -1557,6 +1560,18 @@ func applyDirectory(r *declaration.Directory, previous store.Applied, wasBefore
return out, nil return out, nil
} }
// madeInTheMeshsTree is whether a directory lies in the mesh's own tree (meshDataRoot, below it, never the root
// itself) and belongs to the engine's own account: only that account makes a directory there, so it is the mesh's
// and not found (novox/hq issue 1873). Anywhere else, or owned by any other account, issue 339's rule stands.
func madeInTheMeshsTree(path string, found os.FileInfo) bool {
root := filepath.Clean(meshDataRoot)
if !strings.HasPrefix(filepath.Clean(path), root+string(filepath.Separator)) {
return false
}
uid, _, ok := ownerOf(found)
return ok && uid == os.Geteuid()
}
// applyHandedOver gives a directory the operator handed to the mesh its declared mode and owner (novox/hq issue // applyHandedOver gives a directory the operator handed to the mesh its declared mode and owner (novox/hq issue
// 356). **No link, checked again at the moment of use**: the hand-over refused a link when it was recorded, and a // 356). **No link, checked again at the moment of use**: the hand-over refused a link when it was recorded, and a
// component of the path may have become one since — a parent swapped for a link to /etc would carry root's chmod // component of the path may have become one since — a parent swapped for a link to /etc would carry root's chmod
+5
View File
@@ -124,3 +124,8 @@ func JudgeDirectories(d *declaration.Declaration) string {
placed+refused+guarded+homed, placed, refused, guarded, homed) placed+refused+guarded+homed, placed, refused, guarded, homed)
return strings.Join(append([]string{head}, lines...), "\n") return strings.Join(append([]string{head}, lines...), "\n")
} }
// meshDataRoot is the mesh's own tree, where a module placed "mesh" keeps its directory (<data root>/mesh/<module>):
// the engine's, 0755, so only the engine's account makes a directory in it (novox/hq issue 1873). A variable so a
// test can place it.
var meshDataRoot = "/var/lib/mesh"
+62
View File
@@ -0,0 +1,62 @@
package apply
import (
"context"
"os"
"path/filepath"
"strings"
"testing"
"github.com/novox/mesh-host/internal/store"
)
// novox/hq issue 1873: a module's directory in the mesh's own tree (<data root>/mesh/<module>, place "mesh") that is
// there before its declaration, owned by the engine's own account, was made by the mesh on the way to something below
// it: it is the mesh's, given its declared mode and owner, never used as found. Only the engine's account makes a
// directory in a tree that is the engine's (0755, root); on ace and novox /var/lib/mesh/postgres was there as
// root:root 0700 before postgres's data directory was applied, and the engine left it to root, so the postgres
// provider, running as the operator's account, could not reach its own files.
func TestADirectoryInTheMeshsOwnTreeMadeByTheEngineIsTheMeshs(t *testing.T) {
root := t.TempDir()
before := meshDataRoot
meshDataRoot = filepath.Join(root, "mesh")
t.Cleanup(func() { meshDataRoot = before })
made := filepath.Join(meshDataRoot, "postgres")
if err := os.MkdirAll(made, 0o700); err != nil {
t.Fatal(err)
}
if err := os.Chmod(made, 0o700); err != nil {
t.Fatal(err)
}
decl := `{"id":"postgres.data","type":"directory","path":"` + made + `","mode":"0750"}`
var said []string
_, state, err := Apply(context.Background(), archHost(t), declare(t, decl), store.State{}, store.OriginDeclared,
noServices, func(line string) { said = append(said, line) }, nil)
if err != nil {
t.Fatal(err)
}
if strings.Contains(strings.Join(said, "\n"), "WARNING postgres.data") {
t.Errorf("a directory the engine's account made in the mesh's tree was used as found: %q", said)
}
if a, _ := state.Find("postgres.data"); a.AsFound {
t.Error("it is recorded as found")
}
if info, _ := os.Stat(made); info.Mode().Perm() != 0o750 {
t.Errorf("it was not given its declared mode: %04o", info.Mode().Perm())
}
// Outside the mesh's tree the rule of issue 339 stands: found, used as found.
other := filepath.Join(root, "var-lib", "notes")
if err := os.MkdirAll(other, 0o700); err != nil {
t.Fatal(err)
}
said = nil
decl = `{"id":"notes.data","type":"directory","path":"` + other + `","mode":"0750"}`
if _, _, err := Apply(context.Background(), archHost(t), declare(t, decl), store.State{}, store.OriginDeclared,
noServices, func(line string) { said = append(said, line) }, nil); err != nil {
t.Fatal(err)
}
if !strings.Contains(strings.Join(said, "\n"), "WARNING notes.data") {
t.Errorf("a directory found outside the mesh's tree was not used as found: %q", said)
}
}