Files
mesh-host/internal/apply/placement_guard_test.go
T
jochen a018ebd1ca
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 pass: its merge-check.sh passed
mesh/delivery superseded: a newer head of the same pull request
Pass -- before every unit, and give a unit back only when it is exactly as the mesh held it
Third review of mesh-catalog #147.

A unit's name can now come from a setting (a template's instance), so every systemctl call that names a
unit passes `--` before it, and a name can never be read as an option.

On removal, a unit is started or enabled again only when it is exactly the stopped and disabled the mesh
held it to. A masked unit, or one somebody started or enabled since, is left as it is, and the report
says why. A restore that fails (the start refused, or the unit not running after it) puts back what this
attempt changed and is reported as waiting, so no apply is made fatal by it and the next one tries again.

The tests' fake service managers read systemctl's arguments in the shape they were written for
(oldShape); internal/system's own test holds the `--`.
2026-10-09 01:54:26 +02:00

197 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) {
args = oldShape(name, args)
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)
}
}