Files
mesh-host/internal/apply/placement_guard_test.go
T
jochen 3a002f6e7b
mesh/merge-gate pass: builds mesh-host → ace, g14, novox, shanks; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check fail: its merge-check.sh failed: --- FAIL: TestBelowAHomeOnlyThatAccountsDirectoryIsPlaced (0.00s)
mesh/delivery superseded: a newer head of the same pull request
Refuse a placement or mount at the machine's own trees, and use a found directory as found
A module's places setting could point a directory anywhere, with an owner it
named, and the engine chowned and chmodded whatever it was pointed at as root,
through a link too; its accesses setting could mount any path into a container
(hq issue 339). The engine now refuses, as a failed resource with its reason,
a directory, access or mount source that is or holds one of the machine's
roots, anything placed in the kernel's, the boot loader's, root's or the
engine's own trees, a non-root directory below /etc, /usr or /run, an access
there, another account's directory below a home, and a container mounting
anything it refused, judged with every link resolved. A directory whose last
component is a link is refused, and one found here that the mesh did not make
keeps its owner and mode and is never removed.
2026-10-08 23:58:14 +02:00

196 lines
8.2 KiB
Go

package apply
import (
"context"
"os"
"path/filepath"
"strings"
"testing"
"github.com/novox/mesh-host/internal/store"
)
// Where the node-engine places nothing and mounts nothing, whoever asks (novox/hq issue 339). A module's
// `places` setting moved a directory anywhere, with an owner it named, and the engine — root — made it so: a
// directory at /etc owned by a caller's account handed it /etc. Each test here is a declaration the controller
// might send; the engine refuses it whatever the controller decided.
func applyOnce(t *testing.T, resources string, run Runner, state store.State) (Report, store.State, error) {
t.Helper()
if run == nil {
run = noServices
}
return Apply(context.Background(), archHost(t), declare(t, resources), state, store.OriginDeclared, run, nil, nil)
}
func refusedFor339(t *testing.T, what string, err error) {
t.Helper()
if err == nil || !strings.Contains(err.Error(), "issue 339") || !strings.Contains(err.Error(), "nothing was touched") {
t.Fatalf("%s was not refused as the machine's own: %v", what, err)
}
}
func TestNoDirectoryIsPlacedAtTheMachinesOwn(t *testing.T) {
for _, path := range []string{"/", "/etc", "/etc/", "/usr", "/var", "/var/lib", "/home", "/run", "/boot", "/root",
"/var/lib/mesh-host", "/var/lib/mesh-host/identity", "/proc/1/task", "/sys/kernel", "/dev/shm", "/root/.ssh",
"/srv/../etc"} {
_, _, err := applyOnce(t, `{"id":"notes.data","type":"directory","path":"`+path+`","mode":"0755"}`, nil, store.State{})
refusedFor339(t, path, err)
}
// Below /etc, /usr or /run, a directory is root's: the machine's configuration is read as root's word.
_, _, err := applyOnce(t, `{"id":"notes.data","type":"directory","path":"/etc/sudoers.d","owner":"1001:1001"}`,
nil, store.State{})
refusedFor339(t, "/etc/sudoers.d owned by another account", err)
_, _, err = applyOnce(t, `{"id":"notes.data","type":"directory","path":"/etc/systemd/system/getty@.service.d",`+
`"owner":"1001:1001"}`, nil, store.State{})
refusedFor339(t, "a drop-in directory owned by another account", err)
}
func TestNoAccessIsAtTheMachinesOwn(t *testing.T) {
for _, path := range []string{"/", "/etc", "/etc/ssl", "/usr", "/var/lib", "/home", "/root", "/proc",
"/var/lib/mesh-host", "/run/docker"} {
_, _, err := applyOnce(t, `{"id":"notes.media","type":"access","path":"`+path+`"}`, nil, store.State{})
refusedFor339(t, "an access at "+path, err)
}
}
// A container is never handed the machine's own as a mount, nor a directory or access the engine refused.
func TestNoContainerMountsTheMachinesOwn(t *testing.T) {
ran := false
run := func(ctx context.Context, name string, args ...string) (string, error) {
switch {
case args[0] == "info":
return "27.0\n", nil
case args[0] == "container":
return "", os.ErrNotExist
case args[0] == "run":
ran = true
return "deadbeef\n", nil
}
return "", nil
}
for _, mount := range []string{"/:/host", "/etc:/host-etc", "/var/lib:/data", "/home:/homes:ro",
"/var/lib/mesh-host:/state", "/proc:/p", "/dev:/d", "/root/.ssh:/keys", "/boot/efi:/efi"} {
ran = false
_, _, err := Apply(context.Background(), archHost(t), parseTrusted(t, `{"declaration":1,"resources":[
{"id":"notes.server","type":"container","name":"notes","image":"`+pinned+`","volumes":["`+mount+`"]}
]}`), store.State{}, store.OriginCarried, run, nil, nil)
refusedFor339(t, "a container mounting "+mount, err)
if ran {
t.Fatalf("a container mounting %s was run", mount)
}
}
}
// The path is not followed: a link where a directory is declared, or a link on the way to it, is judged where
// it leads.
func TestALinkIsNotFollowedToTheMachinesOwn(t *testing.T) {
dir := t.TempDir()
target := filepath.Join(dir, "target")
if err := os.Mkdir(target, 0o700); err != nil {
t.Fatal(err)
}
link := filepath.Join(dir, "link")
if err := os.Symlink(target, link); err != nil {
t.Fatal(err)
}
_, _, err := applyOnce(t, `{"id":"notes.data","type":"directory","path":"`+link+`","mode":"0755"}`, nil, store.State{})
refusedFor339(t, "a directory that is a link", err)
if info, _ := os.Stat(target); info.Mode().Perm() != 0o700 {
t.Fatalf("the link's target was changed to %o", info.Mode().Perm())
}
// A link on the way: <dir>/via → /usr, so <dir>/via/share is /usr/share.
via := filepath.Join(dir, "via")
if err := os.Symlink("/usr", via); err != nil {
t.Fatal(err)
}
_, _, err = applyOnce(t, `{"id":"notes.data","type":"directory","path":"`+via+`/share"}`, nil, store.State{})
refusedFor339(t, "a directory reached through a link to /usr", err)
_, _, err = applyOnce(t, `{"id":"notes.media","type":"access","path":"`+via+`"}`, nil, store.State{})
refusedFor339(t, "an access reached through a link to /usr", err)
}
// A directory found here, that the mesh did not make, keeps its owner and mode; one the mesh made keeps the
// declared mode, as before.
func TestAFoundDirectoryIsUsedAsFound(t *testing.T) {
dir := t.TempDir()
found := filepath.Join(dir, "found")
if err := os.Mkdir(found, 0o700); err != nil {
t.Fatal(err)
}
report, state, err := applyOnce(t, `{"id":"notes.data","type":"directory","path":"`+found+`","mode":"0755"}`,
nil, store.State{})
if err != nil {
t.Fatal(err)
}
if info, _ := os.Stat(found); info.Mode().Perm() != 0o700 {
t.Fatalf("a directory the mesh did not make was chmodded to %o", info.Mode().Perm())
}
if len(report.Outcomes) != 1 || !strings.Contains(report.Outcomes[0].Detail, "used as found") {
t.Fatalf("the report does not say it was used as found: %+v", report.Outcomes)
}
if a, _ := state.Find("notes.data"); !a.AsFound {
t.Fatal("the record does not say the directory was found")
}
// Still found at the next apply, and never removed: undeclared, it is forgotten and left.
if _, state, err = applyOnce(t, `{"id":"notes.data","type":"directory","path":"`+found+`","mode":"0755"}`,
nil, state); err != nil {
t.Fatal(err)
}
if info, _ := os.Stat(found); info.Mode().Perm() != 0o700 {
t.Fatalf("a found directory was chmodded at the next apply to %o", info.Mode().Perm())
}
if _, _, err = applyOnce(t, `{"id":"notes.other","type":"directory","path":"`+filepath.Join(dir, "other")+`"}`,
nil, state); err != nil {
t.Fatal(err)
}
if _, err := os.Stat(found); err != nil {
t.Fatalf("a found directory was removed: %v", err)
}
// The mesh's own: made by it, its mode is maintained.
made := filepath.Join(dir, "made")
_, state, err = applyOnce(t, `{"id":"notes.made","type":"directory","path":"`+made+`","mode":"0755"}`, nil, store.State{})
if err != nil {
t.Fatal(err)
}
if err := os.Chmod(made, 0o700); err != nil {
t.Fatal(err)
}
if _, state, err = applyOnce(t, `{"id":"notes.made","type":"directory","path":"`+made+`","mode":"0755"}`,
nil, state); err != nil {
t.Fatal(err)
}
if info, _ := os.Stat(made); info.Mode().Perm() != 0o755 {
t.Fatalf("the mesh's own directory was not given its mode back: %o", info.Mode().Perm())
}
// Moved — a placement changed — onto a directory that was already there: that one is found, not the mesh's.
if _, _, err = applyOnce(t, `{"id":"notes.made","type":"directory","path":"`+found+`","mode":"0755"}`,
nil, state); err != nil {
t.Fatal(err)
}
if info, _ := os.Stat(found); info.Mode().Perm() != 0o700 {
t.Fatalf("a placement moved onto a found directory chmodded it to %o", info.Mode().Perm())
}
}
// The module's own directories keep being placed: below /var/lib, below /etc as root's, a container's mount of
// the machine's docker socket.
func TestAModulesOwnPlacesAreStillPlaced(t *testing.T) {
for _, path := range []string{"/var/lib/notes/data", "/etc/systemd/logind.conf.d", "/var/run/fail2ban",
"/srv/notes", "/mnt/plex/config", "/storage/media", "/var/lib/mesh/registry"} {
if err := refusePath(path, placingDirectory, "notes", ""); err != nil {
t.Errorf("%s: %v", path, err)
}
}
for _, path := range []string{"/var/lib/minio-store", "/var/run/docker.sock", "/services/ollama", "/storage/media",
"/etc/localtime", "/proc/cpuinfo", "/dev/null", "/dev/dri"} {
if err := refusePath(path, placingMount, "notes", ""); err != nil {
t.Errorf("a mount of %s: %v", path, err)
}
}
if err := refusePath("/var/lib/notes/data", placingDirectory, "notes", "1001:2000"); err != nil {
t.Errorf("an owner below /var/lib: %v", err)
}
}