//go:build unix package hostbox import ( "context" "errors" "os" "path/filepath" "testing" "github.com/carlosframework/platform/internal/hostclaim" "github.com/carlosframework/platform/internal/registry" ) func TestEmptyCensusClassifiesSharedEntriesExactly(t *testing.T) { ctx := context.Background() base := t.TempDir() db, err := OpenStopInventoryDB(filepath.Join(base, "inventory.db")) if err != nil { t.Fatal(err) } defer db.Close() reg, err := registry.Open(filepath.Join(base, "registry.db")) if err != nil { t.Fatal(err) } defer reg.Close() launches, err := OpenLaunchJournal(filepath.Join(base, "launch.db")) if err != nil { t.Fatal(err) } defer launches.Close() roots := map[string]string{} for _, name := range []string{"data", "opt", "units"} { roots[name] = filepath.Join(base, name) if err = os.Mkdir(roots[name], 0o700); err != nil { t.Fatal(err) } } at := func(root, rel string) string { return filepath.Join(roots[root], rel) } write := func(path string) { t.Helper() if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { t.Fatal(err) } if err := os.WriteFile(path, []byte("x"), 0o644); err != nil { t.Fatal(err) } } link := func(target, path string) { t.Helper() if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { t.Fatal(err) } if err := os.Symlink(target, path); err != nil { t.Fatal(err) } } write(at("data", "opaque/anything")) write(at("opt", "bin")) write(at("opt", "store/v1/app")) write(at("units", "a.service")) link(at("units", "a.service"), at("units", "multi-user.target.wants/a.service")) shared := map[string][]string{ "data": {"opaque"}, "opt": {CensusFile("bin"), CensusTree("store")}, "units": {CensusFile("a.service"), CensusLink("multi-user.target.wants/a.service", at("units", "a.service"))}, } c := EmptyInstallationCensus{Inventory: db, Registry: reg, Launches: launches, Box: "box", Enrollment: "enrollment", DirectoryRevision: "revision", Roots: roots, SharedPaths: shared} obs := hostclaim.Observed{Host: "shop.example", Claim: &hostclaim.Claim{Holder: &hostclaim.Holder{Incarnation: "inc", Stamp: 1}}} selected := RecordSelection{Host: obs.Host, Incarnation: "inc"} if err = c.CheckLocal(ctx, obs, selected); err != nil { t.Fatalf("classified box: %v", err) } refuse := func(name string, mutate func() func()) { t.Helper() restore := mutate() if err := c.CheckLocal(ctx, obs, selected); !errors.Is(err, ErrTypedInventory) { t.Fatalf("%s: %v", name, err) } restore() if err := c.CheckLocal(ctx, obs, selected); err != nil { t.Fatalf("%s: not restored: %v", name, err) } } remove := func(path string) func() { return func() { if err := os.RemoveAll(path); err != nil { t.Fatal(err) } } } created := func(path string, put func(string)) func() func() { return func() func() { put(path); return remove(path) } } swapped := func(path, target string) func() func() { return func() func() { old, err := os.Readlink(path) if err != nil { t.Fatal(err) } remove(path)() link(target, path) return func() { remove(path)(); link(old, path) } } } refuse("symlink in a shared tree", created(at("opt", "store/v1/sh"), func(p string) { link("/bin/sh", p) })) refuse("host name in a shared tree", created(at("opt", "store/v1/shop.example"), write)) refuse("unclassified file", created(at("opt", "other"), write)) refuse("substituted link target", swapped(at("units", "multi-user.target.wants/a.service"), at("units", "b.service"))) refuse("relative link target", swapped(at("units", "multi-user.target.wants/a.service"), "../a.service")) refuse("foreign link into a managed root", created(at("units", "multi-user.target.wants/b.service"), func(p string) { link(at("opt", "bin"), p) })) refuse("foreign link below a unit-root directory", created(at("units", "multi-user.target.wants/c.service"), func(p string) { link(at("units", "multi-user.target.wants/ssh.service"), p) })) refuse("foreign link naming the platform", created(at("units", "d.service"), func(p string) { link("/srv/carlos/d.service", p) })) refuse("foreign file naming the host", created(at("units", "e.service"), func(p string) { if err := os.WriteFile(p, []byte("ExecStart=/srv/shop.example\n"), 0o644); err != nil { t.Fatal(err) } })) refuse("host-named foreign unit", created(at("units", "x@shop.example.service"), write)) refuse("file where a link is classified", func() func() { path := at("units", "multi-user.target.wants/a.service") remove(path)() write(path) return func() { remove(path)(); link(at("units", "a.service"), path) } }) if err = os.Remove(at("opt", "bin")); err != nil { t.Fatal(err) } link(at("opt", "store/v1/app"), at("opt", "bin")) if err = c.CheckLocal(ctx, obs, selected); !errors.Is(err, ErrTypedInventory) { t.Fatalf("symlink where a file is classified: %v", err) } remove(at("opt", "bin"))() for name, bad := range map[string][]string{"relative link target": {CensusLink("x", "y")}, "escaping entry": {CensusFile("../x")}, "host entry": {CensusFile("shop.example")}, "conflicting entries": {CensusFile("x"), CensusTree("x")}} { c.SharedPaths = map[string][]string{"data": shared["data"], "opt": append(append([]string{}, shared["opt"]...), bad...), "units": shared["units"]} if err = c.CheckLocal(ctx, obs, selected); !errors.Is(err, ErrTypedInventory) { t.Fatalf("%s accepted: %v", name, err) } } }