The module is the one whose catalog entry claims node-engine, else the one this node heard run the build its own fingerprint names; no name is fixed in the guard or in the compose. Builds: design 47 G §364, §527, §428 Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
217 lines
9.0 KiB
Go
217 lines
9.0 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)
|
|
}
|
|
// Design 47 G §364, §527: the node-engine's own trees are placed in by the module the composer says claims its seat,
|
|
// whatever that module is called, and by no other; with none said, by nobody.
|
|
tree := engineTrees[0]
|
|
t.Cleanup(func() { UseEngineModule(nil) })
|
|
for _, c := range []struct {
|
|
engine func() string
|
|
module string
|
|
placed bool
|
|
}{
|
|
{func() string { return "the-engine" }, "the-engine", true},
|
|
{func() string { return "the-engine" }, "notes", false},
|
|
{func() string { return "the-engine" }, "", false},
|
|
{func() string { return "" }, "", false},
|
|
{nil, "the-engine", false},
|
|
} {
|
|
UseEngineModule(c.engine)
|
|
if err := refusePath(tree, placingDirectory, c.module, ""); (err == nil) != c.placed {
|
|
t.Errorf("%s for module %q: %v, want placed %v", tree, c.module, err, c.placed)
|
|
}
|
|
}
|
|
}
|