//go:build linux package apply import ( "context" "encoding/json" "errors" "fmt" "os" "path/filepath" "strconv" "strings" "testing" "time" "golang.org/x/sys/unix" "github.com/novox/mesh-host/internal/declaration" "github.com/novox/mesh-host/internal/store" ) // The engine's half of ADR 0328's checks (novox/hq ADR 0328, "How it is checked", issue 567). The engine runs as // root; a test may not, and a non-root test gives its folders a group it is in itself. // testGID is a group the test may give its folders: any number as root, else one of its own added groups that is // no root group's. func testGID(t *testing.T) int { t.Helper() if os.Geteuid() == 0 { return 61000 } groups, _ := os.Getgroups() for _, g := range groups { if g != os.Getgid() && g != 0 && rootGroup(g) == "" { return g } } t.Skip("the test runs as no root and in no group of its own beside its primary one, so it can give no " + "folder a library's group") return 0 } // libraries has the test keep its own record of libraries and judge mounts as the test says. func libraries(t *testing.T, points, targets []string) { t.Helper() was, wasPoints, wasTargets := Libraries, MountPoints, MountTargets Libraries = NewLibraryMemory() forgetMountHistory() MountPoints = func() []string { return append([]string{"/"}, points...) } MountTargets = func(context.Context) []string { return targets } t.Cleanup(func() { Libraries, MountPoints, MountTargets = was, wasPoints, wasTargets forgetMountHistory() }) } func forgetMountHistory() { mountHistory.Lock() mountHistory.seen = map[string]bool{} mountHistory.Unlock() } // libraryDecl is a declaration with one library, its group, and resources. func libraryDecl(t *testing.T, lib string, resources ...string) *declaration.Declaration { t.Helper() var l declaration.Library if err := jsonInto(lib, &l); err != nil { t.Fatal(err) } raw := fmt.Sprintf(`{"declaration":1,"groups":[{"name":%q,"gid":%d}],"libraries":[%s],"resources":[%s]}`, l.Group, l.GID, lib, strings.Join(resources, ",")) if len(resources) == 0 { raw = strings.Replace(raw, `"resources":[]`, `"owns_nothing":true,"resources":[]`, 1) } d, err := declaration.Parse([]byte(raw)) if err != nil { t.Fatal(err) } return d } // libJSON is a library's declaration. func libJSON(name, root string, gid int, home, taken bool, paths ...string) string { quoted := make([]string, len(paths)) for i, p := range paths { quoted[i] = strconv.Quote(p) } return fmt.Sprintf(`{"library":%q,"root":%q,"group":"mesh-%s","gid":%d,"home":%v,"taken":%v,"paths":[%s]}`, name, root, name, gid, home, taken, strings.Join(quoted, ",")) } // applyLib applies d with a runner that answers the group database for the library group (already there) and runs // nothing else. func applyLib(t *testing.T, d *declaration.Declaration, known store.State) (Report, store.State, error) { t.Helper() run := func(_ context.Context, name string, args ...string) (string, error) { if name == "getent" && args[0] == "group" { for _, g := range d.Groups { if args[1] == g.Name || args[1] == strconv.Itoa(g.GID) { return fmt.Sprintf("%s:x:%d:\n", g.Name, g.GID), nil } } return "", errors.New("getent exited 2") } return "", nil } return Apply(context.Background(), archHost(t), d, known, store.OriginDeclared, run, nil, nil) } func statOf(t *testing.T, path string) unix.Stat_t { t.Helper() var st unix.Stat_t if err := unix.Lstat(path, &st); err != nil { t.Fatal(err) } return st } // Row 6: the root and every requested sub-path kept in the library's group with group rwx and setgid on every apply — // those folders alone, never what is in them; owner, owner's and others' bits unchanged. func TestALibraryIsKeptInItsGroup(t *testing.T) { gid := testGID(t) libraries(t, nil, nil) root := filepath.Join(t.TempDir(), "media") if err := os.Mkdir(root, 0o755); err != nil { t.Fatal(err) } d := libraryDecl(t, libJSON("media", root, gid, true, false, "movies")) if _, _, err := applyLib(t, d, store.State{}); err != nil { t.Fatal(err) } // Kept since it was empty; an app then narrows a requested folder and writes beneath it. movies := filepath.Join(root, "movies") if err := os.Chown(movies, -1, os.Getgid()); err != nil { t.Fatal(err) } if err := unix.Chmod(movies, 0o705); err != nil { t.Fatal(err) } film := filepath.Join(movies, "film") if err := os.WriteFile(film, []byte("data"), 0o640); err != nil { t.Fatal(err) } filmBefore := statOf(t, film) before := statOf(t, movies) report, _, err := applyLib(t, d, store.State{}) if err != nil { t.Fatal(err) } if o := outcomeOf(report, "library:media"); o.Action != "updated" { t.Fatalf("a kept library was not put back: %+v", o) } after := statOf(t, movies) if int(after.Gid) != gid || after.Mode&0o7777 != 0o2775 { t.Fatalf("a requested path is not kept in the group with group rwx and setgid: gid %d, mode %o", after.Gid, after.Mode&0o7777) } if after.Uid != before.Uid || after.Mode&0o707 != before.Mode&0o707 { t.Errorf("the owner or the owner's and others' bits changed: %o to %o", before.Mode&0o7777, after.Mode&0o7777) } if f := statOf(t, film); f.Mode != filmBefore.Mode || f.Gid != filmBefore.Gid { t.Error("a file under a kept path was changed without the take act") } if rec, _ := libraryRecord("media"); !rec.Kept || rec.How != store.KeptEmpty { t.Errorf("not recorded kept as empty: %+v", rec) } } // Row 9: a missing sub-path is made with the root's owner, the library's group, group rwx and setgid. func TestAMissingSubPathIsMadeAsTheRootIs(t *testing.T) { gid := testGID(t) libraries(t, nil, nil) root := filepath.Join(t.TempDir(), "media") if err := os.Mkdir(root, 0o751); err != nil { t.Fatal(err) } if err := os.Chmod(root, 0o751); err != nil { t.Fatal(err) } d := libraryDecl(t, libJSON("media", root, gid, true, false, "tv/shows")) if _, _, err := applyLib(t, d, store.State{}); err != nil { t.Fatalf("an empty root was not kept: %v", err) } rst := statOf(t, root) for _, p := range []string{filepath.Join(root, "tv"), filepath.Join(root, "tv", "shows")} { st := statOf(t, p) if st.Uid != rst.Uid || int(st.Gid) != gid || st.Mode&0o7777 != 0o2771 { t.Errorf("%s made as uid %d gid %d mode %o, and the root's owner, the group and 2771 were wanted", p, st.Uid, st.Gid, st.Mode&0o7777) } } if rec, _ := libraryRecord("media"); rec.How != store.KeptEmpty { t.Errorf("an empty root is not kept as empty: %+v", rec) } } // Row 7 (keep wait) and §8: a root that holds data not in the group waits with reason keep, and nothing is changed. func TestARootThatHoldsDataWaitsForTheOperatorsWord(t *testing.T) { gid := testGID(t) libraries(t, nil, nil) root := filepath.Join(t.TempDir(), "media") if err := os.MkdirAll(filepath.Join(root, "movies"), 0o755); err != nil { t.Fatal(err) } before := statOf(t, root) d := libraryDecl(t, libJSON("media", root, gid, true, false, "movies")) report, _, err := applyLib(t, d, store.State{}) if err != nil { t.Fatal(err) } if o := outcomeOf(report, "library:media"); o.Action != "waiting" || !strings.Contains(o.Detail, KeepWords("media")) { t.Fatalf("a root holding data did not wait for the operator: %+v", o) } if after := statOf(t, root); after.Gid != before.Gid || after.Mode != before.Mode { t.Fatal("the root was changed without a warrant") } j := JudgeLibrary(context.Background(), d.Libraries[0]) if j.Kind != VerdictWaitKeep || j.Placed == "" || strings.Contains(KeepWords("media")+DiskWords("media"), "/") { t.Fatalf("the look does not wait for the operator, naming the filesystem with no path: %+v", j) } } // Row 7, the act: group, group write and setgid set under the root; no link followed; no other filesystem entered; // a file with another name elsewhere left; owner, owner's and others' bits unchanged; nothing without a warrant // (the act's ask, internal/link); an undeclared library refused; a walk over its bound goes on from its cursor. func TestTheTakeActSetsTheGroupUnderTheRoot(t *testing.T) { gid := testGID(t) libraries(t, nil, nil) base := t.TempDir() root := filepath.Join(base, "media") outside := filepath.Join(base, "outside") for _, d := range []string{filepath.Join(root, "movies", "a film"), outside} { if err := os.MkdirAll(d, 0o705); err != nil { t.Fatal(err) } } file := filepath.Join(root, "movies", "a film", "film.mkv") if err := os.WriteFile(file, []byte("data"), 0o604); err != nil { t.Fatal(err) } if err := unix.Chmod(file, 0o4604); err != nil { // its own setuid bit, which a group change drops t.Fatal(err) } secret := filepath.Join(outside, "secret") if err := os.WriteFile(secret, nil, 0o600); err != nil { t.Fatal(err) } if err := os.Link(secret, filepath.Join(root, "movies", "hardlink")); err != nil { t.Fatal(err) } if os.Geteuid() == 0 { // an account's file, as an *arr app's hardlinked download is if err := os.Chown(secret, 65534, -1); err != nil { t.Fatal(err) } } if err := os.Symlink(outside, filepath.Join(root, "link")); err != nil { t.Fatal(err) } d := libraryDecl(t, libJSON("media", root, gid, true, false, "movies")) if _, _, err := applyLib(t, d, store.State{}); err != nil { t.Fatal(err) } if _, _, err := TakeLibrary(context.Background(), d, "library:books", root, "w", "op"); err == nil { t.Fatal("an undeclared library was taken") } // Bounded: three entries a walk, gone on from the cursor until done. was := ActLimits ActLimits = WalkLimits{Entries: 3, Time: time.Minute} defer func() { ActLimits = was }() done, said, err := TakeLibrary(context.Background(), d, "library:media", root, "w-1", "the operator") if err != nil { t.Fatalf("the take act was refused: %v", err) } <-done if !strings.Contains(said, "w-1") { t.Errorf("the act does not say its warrant: %s", said) } rec, _ := libraryRecord("media") if rec.Act == nil || rec.Act.State != "stopped" || len(rec.Cursor) == 0 { t.Fatalf("a walk over its bound does not say where it stopped: %+v %+v", rec.Act, rec.Cursor) } for i := 0; i < 10; i++ { ReconcileLibraries(context.Background(), d) if rec, _ = libraryRecord("media"); rec.Act.State == "done" { break } } if rec.Act.State != "done" || rec.Act.Linked != 1 || rec.Act.Stripped != 1 || !rec.Kept || rec.How != store.KeptByWarrant || rec.KeptOn == "" { t.Fatalf("the act is not recorded done under its warrant on its filesystem: %+v %+v", rec, rec.Act) } for _, p := range []string{root, filepath.Join(root, "movies"), filepath.Join(root, "movies", "a film")} { if st := statOf(t, p); int(st.Gid) != gid || st.Mode&0o7777 != 0o2775 { t.Errorf("%s: gid %d mode %o, and the group with 2775 (705 plus group rwx and setgid) was wanted", p, st.Gid, st.Mode&0o7777) } } if st := statOf(t, file); int(st.Gid) != gid || st.Mode&0o7777 != 0o0664 { t.Errorf("a file: gid %d mode %o, and the group with 0664 (its setuid bit taken by the group change and "+ "never put back) was wanted", st.Gid, st.Mode&0o7777) } if st := statOf(t, secret); int(st.Gid) != gid || st.Mode&0o7777 != 0o660 { t.Error("an account's file with another name (a hardlinked download) was not given the group") } if st := statOf(t, outside); int(st.Gid) == gid { t.Error("the act followed a link out of the library") } } // Row 7, the act: another filesystem is not entered — a folder whose device is not the root's is passed by. func TestTheTakeActEntersNoOtherFilesystem(t *testing.T) { gid := testGID(t) root := t.TempDir() mount := filepath.Join(root, "elsewhere") if err := os.Mkdir(mount, 0o755); err != nil { t.Fatal(err) } stats := walkStats{ at: func(dirfd int, name string, st *unix.Stat_t) error { err := unix.Fstatat(dirfd, name, st, unix.AT_SYMLINK_NOFOLLOW) if name == "elsewhere" { st.Dev++ } return err }, fd: unix.Fstat, } w, err := walkRoot(root, ActLimits, stats, nil, groupVisit(gid)) if err != nil { t.Fatal(err) } if w.counts.Mounts != 1 || int(statOf(t, mount).Gid) == gid { t.Fatalf("the walk entered another filesystem: %+v", w.counts) } } // Row 9: a root that is absent, or whose disk is not mounted, makes nothing, holds the containers that mount it, // and waits with reason disk; off its home, a mount that is not there holds them too. func TestALibraryWhoseDiskIsNotThereHoldsItsContainers(t *testing.T) { gid := testGID(t) base := t.TempDir() disk := filepath.Join(base, "disk") if err := os.Mkdir(disk, 0o755); err != nil { t.Fatal(err) } for _, tc := range []struct { why, root string home bool targets []string }{ {"its root absent", filepath.Join(base, "gone", "media"), true, nil}, {"its disk not mounted", filepath.Join(disk, "media"), true, []string{disk}}, {"its mount not there", disk, false, nil}, } { libraries(t, nil, tc.targets) d := libraryDecl(t, libJSON("media", tc.root, gid, tc.home, false, "movies"), `{"id":"radarr.app","type":"container","name":"radarr","image":"`+pinned+`","volumes":["`+ filepath.Join(tc.root, "movies")+`:/movies"]}`) var started bool run := func(_ context.Context, name string, args ...string) (string, error) { if len(args) > 0 && args[0] == "run" { started = true } if name == "getent" && args[0] == "group" { return fmt.Sprintf("mesh-media:x:%d:\n", gid), nil } return "", nil } report, _, _ := Apply(context.Background(), archHost(t), d, store.State{}, store.OriginDeclared, run, nil, nil) if o := outcomeOf(report, "library:media"); o.Action != "waiting" || !strings.Contains(o.Detail, DiskWords("media")) { t.Errorf("%s: the library does not wait for its disk: %+v", tc.why, o) } if o := outcomeOf(report, "radarr.app"); o.Action != "waiting" || started { t.Errorf("%s: a container that mounts it was not held: %+v", tc.why, o) } if !WaitingOnLibrary(d)["radarr.app"] { t.Errorf("%s: liveness is not told the container waits", tc.why) } if j := JudgeLibrary(context.Background(), d.Libraries[0]); j.Kind != VerdictWaitDisk { t.Errorf("%s: the look judges it %s", tc.why, j.Kind) } if _, err := os.Stat(filepath.Join(tc.root, "movies")); !errors.Is(err, os.ErrNotExist) { t.Errorf("%s: something was made", tc.why) } } } // Row 3: the library's group made with its number; a name or number that is another's refused; what was made, and // only that, removed when no longer declared. func TestALibrarysGroupIsMadeWithItsNumber(t *testing.T) { m := &accountsMachine{users: map[string][2]int{}, groups: map[string][2]int{"media": {2000, 0}}} d := withGroups(t, `{"name":"mesh-media","gid":62000}`, ``, ``) report, known, err := Apply(context.Background(), archHost(t), d, store.State{}, store.OriginDeclared, m.run, nil, nil) if err != nil { t.Fatal(err) } if strings.Join(m.ran, "\n") != "groupadd --system --gid 62000 mesh-media" { t.Fatalf("made with: %v", m.ran) } if o := outcomeOf(report, "group mesh-media"); o.Action != "created" || len(known.Groups) != 1 { t.Fatalf("not recorded as made: %+v %+v", o, known.Groups) } clash := withGroups(t, `{"name":"mesh-books","gid":2000}`, ``, ``) if _, _, err := Apply(context.Background(), archHost(t), clash, known, store.OriginDeclared, m.run, nil, nil); err == nil || !strings.Contains(err.Error(), "media") { t.Errorf("a number that is another group's was not refused naming it: %v", err) } m.ran = nil none := withGroups(t, ``, ``, `{"id":"x.d","type":"directory","path":"`+t.TempDir()+`/x"}`) if _, known, err = Apply(context.Background(), archHost(t), none, known, store.OriginDeclared, m.run, nil, nil); err != nil { t.Fatal(err) } if _, still := m.groups["mesh-media"]; still || len(known.Groups) != 0 { t.Fatalf("a library group no longer declared was not removed: %v", m.ran) } if _, kept := m.groups["media"]; !kept { t.Fatal("a group the mesh did not make was removed") } } // Row 4: a module's user with its own group (its uid's number), its first library's group as primary and its other // libraries' as added groups. func TestAModulesUserIsInItsLibrariesGroups(t *testing.T) { m := &accountsMachine{users: map[string][2]int{}, groups: map[string][2]int{}} d := withGroups(t, `{"name":"mesh-media","gid":62000},{"name":"mesh-books","gid":62001}`, `{"module":"radarr","name":"mesh-radarr","uid":61001,"gid":62000,"groups":[62001]}`, ``) if _, known, err := Apply(context.Background(), archHost(t), d, store.State{}, store.OriginDeclared, m.run, nil, nil); err != nil { t.Fatal(err) } else if len(known.Identities) != 1 { t.Fatalf("the identity is not recorded: %+v", known.Identities) } want := []string{ "groupadd --system --gid 62000 mesh-media", "groupadd --system --gid 62001 mesh-books", "groupadd --system --gid 61001 mesh-radarr", "useradd --system --uid 61001 --gid 62000 --groups mesh-books --no-create-home --home-dir /nonexistent --shell " + "/usr/bin/nologin mesh-radarr", } if strings.Join(m.ran, "\n") != strings.Join(want, "\n") { t.Fatalf("made with:\n%s\nwant:\n%s", strings.Join(m.ran, "\n"), strings.Join(want, "\n")) } } // Row 11: the tool runner's account is put in each library's group once, its unit restarted for it (a change of // library:), and taken out once no library puts it there. func TestTheToolRunnerIsInItsLibrariesGroups(t *testing.T) { gid := testGID(t) libraries(t, []string{"/srv/media"}, nil) m := &accountsMachine{users: map[string][2]int{"nox": {1003, 1003}}, groups: map[string][2]int{"nox": {1003, 0}}} lib := `{"library":"media","root":"/srv/media","group":"mesh-media","gid":` + strconv.Itoa(gid) + `,"home":false,"runner":"nox"}` d := libraryDecl(t, lib) report, known, err := Apply(context.Background(), archHost(t), d, store.State{}, store.OriginDeclared, m.run, nil, nil) if err != nil { t.Fatal(err) } if !strings.Contains(strings.Join(m.ran, "\n"), "usermod --append --groups mesh-media nox") { t.Fatalf("the tool runner was not put in the library's group: %v", m.ran) } if o := outcomeOf(report, "library:media"); o.Action != "updated" || len(known.Members) != 1 { t.Fatalf("its joining is not a change of library:media, or not recorded: %+v %+v", o, known.Members) } m.ran = nil if _, known, err = Apply(context.Background(), archHost(t), d, known, store.OriginDeclared, m.run, nil, nil); err != nil { t.Fatal(err) } if strings.Contains(strings.Join(m.ran, "\n"), "usermod") { t.Fatal("the tool runner was put in its group again") } none := withGroups(t, ``, ``, `{"id":"x.d","type":"directory","path":"`+t.TempDir()+`/x"}`) if _, known, err = Apply(context.Background(), archHost(t), none, known, store.OriginDeclared, m.run, nil, nil); err != nil { t.Fatal(err) } if !strings.Contains(strings.Join(m.ran, "\n"), "gpasswd --delete nox mesh-media") || len(known.Members) != 0 { t.Fatalf("the tool runner was not taken out of a group no library puts it in: %v", m.ran) } } // Row 15: a library group, an identity's added group or an image told a root group's number is refused with its // reason, and nothing is made or started. func TestNoLibraryGroupIsRoot(t *testing.T) { was := RootGroupIDs RootGroupIDs = func() map[int]string { return map[int]string{10: "wheel"} } defer func() { RootGroupIDs = was }() dir := t.TempDir() for _, d := range []*declaration.Declaration{ withGroups(t, `{"name":"mesh-media","gid":10}`, ``, `{"id":"m.d","type":"directory","path":"`+dir+`/d"}`), withGroups(t, `{"name":"mesh-media","gid":10}`, `{"module":"radarr","name":"mesh-radarr","uid":61001,"gid":61001,"groups":[10]}`, `{"id":"m.d","type":"directory","path":"`+dir+`/d"}`), } { m := &accountsMachine{users: map[string][2]int{}, groups: map[string][2]int{}} _, _, err := Apply(context.Background(), archHost(t), d, store.State{}, store.OriginDeclared, m.run, nil, nil) if err == nil || !strings.Contains(err.Error(), "wheel") { t.Errorf("a root group was not refused naming why: %v", err) } if len(m.ran) != 0 { t.Errorf("something was made: %v", m.ran) } if _, err := os.Stat(filepath.Join(dir, "d")); err == nil { t.Error("a resource of a declaration naming a root group was applied") } } if _, err := declaration.Parse([]byte(`{"declaration":1,"groups":[{"name":"mesh-media","gid":0}],` + `"owns_nothing":true,"resources":[]}`)); err == nil || !strings.Contains(err.Error(), "never root") { t.Errorf("a library group of number 0 was not refused on arrival: %v", err) } } // A library's filesystem that cannot be named this time is unknown, never a move; another filesystem at the root is. func TestALibraryNotNamedIsUnknownAndAnotherFilesystemIsAMove(t *testing.T) { gid := testGID(t) base := t.TempDir() pool := filepath.Join(base, "pool") root := filepath.Join(pool, "media") if err := os.MkdirAll(filepath.Join(root, "old"), 0o755); err != nil { t.Fatal(err) } libraries(t, []string{pool}, nil) uuid := "6f1c0a4e-1b2d-4c3e-8f00-aa11bb22cc33" wasUUID := MountUUID MountUUID = func(p string) string { if p == pool { return uuid } return "x" } defer func() { MountUUID = wasUUID }() d := libraryDecl(t, libJSON("media", root, gid, true, false)) if _, _, err := applyLib(t, d, store.State{}); err != nil { t.Fatal(err) } done, _, err := TakeLibrary(context.Background(), d, "library:media", root, "w-1", "op") if err != nil { t.Fatal(err) } <-done taken := libraryDecl(t, libJSON("media", root, gid, true, true)) before, _ := libraryRecord("media") uuid = "" // nothing names it now wasFsid := MountFsid MountFsid = func(string) string { return "" } wasSource := MountSource MountSource = func(string) (string, string) { return "", "" } report, _, _ := applyLib(t, taken, store.State{}) MountFsid, MountSource = wasFsid, wasSource if o := outcomeOf(report, "library:media"); o.Action != "waiting" || !strings.Contains(o.Detail, "could not be named") { t.Fatalf("an unnamed filesystem is not said so: %+v", o) } if after, _ := libraryRecord("media"); !after.Kept || after.KeptOn != before.KeptOn { t.Fatalf("a name not read reset the record: %+v", after) } uuid = "00000000-1111-2222-3333-444444444444" // another pool at the same point, its root not in the group if err := os.Chown(root, -1, os.Getgid()); err != nil { t.Fatal(err) } if err := unix.Chmod(root, 0o755); err != nil { t.Fatal(err) } report, _, _ = applyLib(t, taken, store.State{}) if o := outcomeOf(report, "library:media"); o.Action != "waiting" || !strings.Contains(o.Detail, KeepWords("media")) { t.Fatalf("another filesystem at the root was kept on the old warrant: %+v", o) } } // Old-shape parity: a controller older than ADR 0328 sends no groups, identities or libraries, and an access is // applied exactly as before ADR 0325 — mounted when present, refused when absent, nothing kept, made or held. func TestAnOlderControllersDeclarationIsAppliedAsBefore(t *testing.T) { libraries(t, nil, nil) dir := t.TempDir() present := filepath.Join(dir, "library") absent := filepath.Join(dir, "media", "movies") if err := os.Mkdir(present, 0o755); err != nil { t.Fatal(err) } before := statOf(t, present) resources := []string{ `{"id":"radarr.library","type":"access","path":"` + present + `","mode":"read-write","users":[` + `{"identity":"mesh-radarr","uid":61001,"use":"read-write","from":"container"}],"taken":true}`, `{"id":"plex.movies","type":"access","path":"` + absent + `"}`, } report, _, err := Apply(context.Background(), archHost(t), declare(t, strings.Join(resources, ",")), store.State{}, store.OriginDeclared, noServices, nil, nil) if err == nil || !strings.Contains(err.Error(), absent+" is not there, and the mesh does not own it — the operator "+ "must provide it") { t.Fatalf("an absent access is not refused as before: %v", err) } if o := outcomeOf(report, "radarr.library"); o.Action != "unchanged" || o.Detail != "operator-owned; present, read-write, nothing managed" { t.Errorf("a present access is not as before: %+v", o) } if after := statOf(t, present); after.Gid != before.Gid || after.Mode != before.Mode { t.Error("a present access was changed") } if n, _ := unix.Getxattr(present, "system.posix_acl_access", nil); n > 0 { t.Error("an ACL was set on an access") } if _, err := os.Stat(absent); !errors.Is(err, os.ErrNotExist) { t.Error("an absent access was made") } } func jsonInto(raw string, into any) error { return json.Unmarshal([]byte(raw), into) } // Row 4: a module that joins a library later is given its group as primary on the account the mesh made; an account // the mesh did not make is never changed, and is refused if it is not as declared. func TestAModuleThatJoinsALibraryLaterIsGivenItsGroup(t *testing.T) { m := &accountsMachine{users: map[string][2]int{}, groups: map[string][2]int{}} own := withGroups(t, `{"name":"mesh-media","gid":62000}`, `{"module":"radarr","name":"mesh-radarr","uid":61001,"gid":61001}`, ``) _, known, err := Apply(context.Background(), archHost(t), own, store.State{}, store.OriginDeclared, m.run, nil, nil) if err != nil { t.Fatal(err) } m.ran = nil joined := withGroups(t, `{"name":"mesh-media","gid":62000}`, `{"module":"radarr","name":"mesh-radarr","uid":61001,"gid":62000}`, ``) if _, known, err = Apply(context.Background(), archHost(t), joined, known, store.OriginDeclared, m.run, nil, nil); err != nil { t.Fatal(err) } if strings.Join(m.ran, "\n") != "usermod --gid 62000 --groups mesh-radarr" || m.users["mesh-radarr"][1] != 62000 { t.Fatalf("the mesh's account was not given its library's group: %v", m.ran) } theirs := &accountsMachine{users: map[string][2]int{"mesh-radarr": {61001, 61001}}, groups: map[string][2]int{"mesh-radarr": {61001, 0}, "mesh-media": {62000, 0}}} if _, _, err := Apply(context.Background(), archHost(t), joined, store.State{}, store.OriginDeclared, theirs.run, nil, nil); err == nil || !strings.Contains(err.Error(), "was not made by the mesh") { t.Fatalf("an account the mesh did not make was changed, or not refused: %v %v", err, theirs.ran) } } // A library's root is never in the machine's own trees, as an access's never is (novox/hq issue 339). func TestALibraryIsNeverTheMachinesOwnTree(t *testing.T) { gid := testGID(t) libraries(t, nil, nil) for _, root := range []string{"/etc", "/usr/share", "/var/lib/mesh-host/x"} { d := libraryDecl(t, libJSON("sys", root, gid, true, true)) if _, _, err := applyLib(t, d, store.State{}); err == nil { t.Errorf("a library at %s was not refused", root) } if _, _, err := TakeLibrary(context.Background(), d, "library:sys", root, "w", "op"); err == nil { t.Errorf("a library at %s was taken", root) } } } // §7: a root in shape with old data beneath it not in the group is asked about, never kept without a warrant; a root // and sample all in shape are kept; once taken, never asked again on that filesystem. func TestARootInShapeWithDataBelowNotInTheGroupWaits(t *testing.T) { gid := testGID(t) libraries(t, nil, nil) root := filepath.Join(t.TempDir(), "media") movies := filepath.Join(root, "movies") if err := os.MkdirAll(movies, 0o755); err != nil { t.Fatal(err) } old := filepath.Join(movies, "old film") if err := os.WriteFile(old, []byte("data"), 0o644); err != nil { t.Fatal(err) } for _, p := range []string{root, movies} { if err := os.Chown(p, -1, gid); err != nil { t.Fatal(err) } if err := unix.Chmod(p, 0o2775); err != nil { t.Fatal(err) } } d := libraryDecl(t, libJSON("media", root, gid, true, false, "movies")) report, _, err := applyLib(t, d, store.State{}) if err != nil { t.Fatal(err) } if o := outcomeOf(report, "library:media"); o.Action != "waiting" || !strings.Contains(o.Detail, KeepWords("media")) { t.Fatalf("a root in shape with an old file below not in the group was kept without asking: %+v", o) } // The old file in the group too: the sample is in shape, and the library is kept. if err := os.Chown(old, -1, gid); err != nil { t.Fatal(err) } if err := unix.Chmod(old, 0o664); err != nil { t.Fatal(err) } if report, _, _ = applyLib(t, d, store.State{}); outcomeOf(report, "library:media").Action == "waiting" { t.Fatal("a root and sample in shape were not kept") } // Once kept, a file an app later leaves out of the group asks nothing again on that filesystem. if err := os.WriteFile(filepath.Join(movies, "new"), nil, 0o600); err != nil { t.Fatal(err) } if report, _, _ = applyLib(t, d, store.State{}); outcomeOf(report, "library:media").Action == "waiting" { t.Fatal("a kept library asked again") } } // Review: a file with another name that root owns is left as it is (its other name may be anywhere), and a setgid // file never keeps its setgid bit once regrouped. func TestRootsLinkedFilesAreLeftAndSetgidIsNeverKept(t *testing.T) { gid := testGID(t) f, err := os.CreateTemp(t.TempDir(), "file") if err != nil { t.Fatal(err) } defer f.Close() if err := unix.Chmod(f.Name(), 0o2755); err != nil { t.Fatal(err) } st := statOf(t, f.Name()) rootLinked := st rootLinked.Uid, rootLinked.Nlink = 0, 2 if did, err := groupVisit(gid)(int(f.Fd()), rootLinked, false); err != nil || did != visitShared { t.Fatalf("a root-owned file with another name was not left: %d %v", did, err) } if after := statOf(t, f.Name()); after.Gid != st.Gid || after.Mode != st.Mode { t.Fatal("a root-owned file with another name was changed") } did, err := groupVisit(gid)(int(f.Fd()), st, false) if err != nil || did&visitStripped == 0 { t.Fatalf("a setgid file's bit is not said taken: %d %v", did, err) } if after := statOf(t, f.Name()); after.Mode&(unix.S_ISGID|unix.S_ISUID) != 0 || int(after.Gid) != gid { t.Fatalf("a setgid file kept its bit in the library's group: %o", after.Mode&0o7777) } } // Review: a sub-path asked for after the library was kept without a warrant, which holds data not in the group, // waits for the operator's word as the root would, and nothing of it is changed; an empty one is kept. func TestASubPathHoldingOldDataWaitsForTheOperatorsWord(t *testing.T) { gid := testGID(t) libraries(t, nil, nil) root := filepath.Join(t.TempDir(), "media") if err := os.Mkdir(root, 0o755); err != nil { t.Fatal(err) } if _, _, err := applyLib(t, libraryDecl(t, libJSON("media", root, gid, true, false, "movies")), store.State{}); err != nil { t.Fatal(err) } // Someone puts old data at a sub-path the library is asked for only now. music := filepath.Join(root, "music") if err := os.MkdirAll(filepath.Join(music, "album"), 0o755); err != nil { t.Fatal(err) } before := statOf(t, music) report, _, err := applyLib(t, libraryDecl(t, libJSON("media", root, gid, true, false, "movies", "music")), store.State{}) if err != nil { t.Fatal(err) } if o := outcomeOf(report, "library:media"); o.Action != "waiting" || !strings.Contains(o.Detail, KeepWords("media")) { t.Fatalf("a sub-path holding old data was kept without a warrant: %+v", o) } if after := statOf(t, music); after.Gid != before.Gid || after.Mode != before.Mode { t.Fatal("a sub-path holding old data was changed") } // An empty one asked for is kept. if err := os.RemoveAll(filepath.Join(music, "album")); err != nil { t.Fatal(err) } report, _, _ = applyLib(t, libraryDecl(t, libJSON("media", root, gid, true, false, "movies", "music")), store.State{}) if o := outcomeOf(report, "library:media"); o.Action == "waiting" { t.Fatalf("an empty sub-path was not kept: %+v", o) } } // Review: an apply does not wait for a take walk that is running: it leaves the library to it. func TestAnApplyDoesNotWaitForATakeWalk(t *testing.T) { gid := testGID(t) libraries(t, nil, nil) root := filepath.Join(t.TempDir(), "media") if err := os.Mkdir(root, 0o755); err != nil { t.Fatal(err) } lock := libraryLock("media") lock.Lock() defer lock.Unlock() libraryWalks.Store("media", "fs-test") defer libraryWalks.Delete("media") done := make(chan Report, 1) go func() { r, _, _ := applyLib(t, libraryDecl(t, libJSON("media", root, gid, true, false)), store.State{}) done <- r }() select { case r := <-done: if o := outcomeOf(r, "library:media"); !strings.Contains(o.Detail, "take act") { t.Fatalf("the apply does not say it left the library to the walk: %+v", o) } case <-time.After(2 * time.Second): t.Fatal("the apply waited for the take walk") } } // Review: a root in shape whose data beneath — in no requested sub-path — is not in the group waits: the sample, not // the root alone, says whether the data is the library's already (§7). func TestTheSampleBeneathARootInShapeIsJudged(t *testing.T) { gid := testGID(t) libraries(t, nil, nil) root := filepath.Join(t.TempDir(), "media") if err := os.MkdirAll(filepath.Join(root, "old"), 0o755); err != nil { t.Fatal(err) } if err := os.WriteFile(filepath.Join(root, "old", "film"), []byte("data"), 0o644); err != nil { t.Fatal(err) } if err := os.Chown(root, -1, gid); err != nil { t.Fatal(err) } if err := unix.Chmod(root, 0o2775); err != nil { t.Fatal(err) } report, _, err := applyLib(t, libraryDecl(t, libJSON("media", root, gid, true, false)), store.State{}) if err != nil { t.Fatal(err) } if o := outcomeOf(report, "library:media"); o.Action != "waiting" { t.Fatalf("a root in shape with data beneath not in the group was kept: %+v", o) } } // Review: an apply that finds the library's lock held by something that is no take walk (a take act about to be // refused) waits for it and judges the library as always: a library whose disk is not there still holds. func TestAnApplyWaitsForALockThatIsNoWalk(t *testing.T) { gid := testGID(t) libraries(t, nil, nil) root := filepath.Join(t.TempDir(), "gone", "media") lock := libraryLock("media") lock.Lock() go func() { time.Sleep(50 * time.Millisecond); lock.Unlock() }() report, _, _ := applyLib(t, libraryDecl(t, libJSON("media", root, gid, true, false)), store.State{}) if o := outcomeOf(report, "library:media"); o.Action != "waiting" || !strings.Contains(o.Detail, DiskWords("media")) { t.Fatalf("a library whose root is not there was read as kept while its lock was held: %+v", o) } }