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
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.
196 lines
8.2 KiB
Go
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)
|
|
}
|
|
}
|