package hostbox import ( "errors" "path/filepath" "testing" "github.com/carlosframework/platform/internal/registry" ) func TestPlan9Fix2InventoryNamespace(t *testing.T) { data := t.TempDir() row := registry.Route{Host: "app.example", DBPath: filepath.Join(data, "instances", "app.example", "app.example.db"), ClaimOwnership: registry.ClaimOwnership{ClaimIncarnation: "inc", ClaimStamp: 1, ClaimOwner: "owner", ClaimRowID: "row"}} base := StopInventory{Host: row.Host, Incarnation: row.ClaimIncarnation, InstallationStamp: row.ClaimStamp, Complete: true} for _, tc := range []struct { name, kind, root, path, id string want bool }{ {"state parent itself", "path", "data", "instances/app.example", "", true}, {"child", "path", "data", "instances/app.example/app.example.db", "", true}, {"nested child", "path", "data", "instances/app.example/sub/file", "", true}, {"sibling host dir", "path", "data", "instances/other.example", "", false}, {"sibling host file", "path", "data", "instances/other.example/other.example.db", "", false}, {"prefix sibling child", "path", "data", "instances/app.example-other/app.example.db", "", false}, {"not clean dot", "path", "data", "instances/app.example/./x", "", false}, {"not clean double slash", "path", "data", "instances/app.example//x", "", false}, {"not clean trailing slash", "path", "data", "instances/app.example/x/", "", false}, {"traversal back in", "path", "data", "instances/app.example/../app.example/x", "", false}, {"traversal out", "path", "data", "../x", "", false}, {"absolute", "path", "data", "/instances/app.example/x", "", false}, {"absolute data path", "path", "data", filepath.ToSlash(filepath.Join(data, "instances", "app.example", "x")), "", false}, {"root registry db", "path", "data", "registry.db", "", false}, {"root registry db-wal", "path", "data", "registry.db-wal", "", false}, {"root registry db-shm", "path", "data", "registry.db-shm", "", false}, {"root registry db-journal", "path", "data", "registry.db-journal", "", false}, {"flat db name under nested", "path", "data", "app.example.db", "", false}, {"route", "route", "registry", "app.example", "row", true}, {"timer", "timer", "registry", "app.example/daily", "timer", true}, {"foreign state", "path", "data", "instances/other.example", "", false}, {"prefix sibling", "path", "data", "instances/app.example-other", "", false}, {"data root", "path", "data", ".", "", false}, {"escape", "path", "data", "instances/app.example/../other.example", "", false}, {"foreign route", "route", "registry", "other.example", "row", false}, {"foreign row id", "route", "registry", "app.example", "other-row", false}, {"foreign timer", "timer", "registry", "other.example/daily", "timer", false}, {"unit", "path", "units", "carlos-edge.service.d/guard.conf", "", false}, {"binary", "path", "opt", "carlos", "", false}, {"unknown kind", "unit-state", "data", "instances/app.example", "", false}, } { t.Run(tc.name, func(t *testing.T) { in := base in.Entries = []StopInventoryEntry{{Kind: tc.kind, ManagedRoot: tc.root, RelativePath: filepath.FromSlash(tc.path), RowIdentity: tc.id, Ownership: "owner", Materialization: "materialized"}} err := ValidateAgentInventory(in, &row, data) if (err == nil) != tc.want { t.Fatalf("allowed=%v: %v", tc.want, err) } if !tc.want && !errors.Is(err, ErrTypedInventory) { t.Fatalf("rejection is not ErrTypedInventory: %v", err) } }) } // A missing row can record the initial route intent, but grants no path authority. base.Entries = []StopInventoryEntry{{Kind: "route", ManagedRoot: "registry", RelativePath: row.Host, RowIdentity: "new-row", Ownership: "owner", Materialization: "intended"}} if err := ValidateAgentInventory(base, nil, data); err != nil { t.Fatal(err) } base.Entries[0] = StopInventoryEntry{Kind: "path", ManagedRoot: "data", RelativePath: "instances/app.example", Ownership: "owner", Materialization: "intended"} if err := ValidateAgentInventory(base, nil, data); err == nil { t.Fatal("missing row granted path authority") } row.DBPath = filepath.Join(data, row.Host+".db") for _, name := range []string{row.Host + ".db", row.Host + ".db-wal", row.Host + ".db-shm", row.Host + ".db-journal"} { base.Entries[0].RelativePath = name if err := ValidateAgentInventory(base, &row, data); err != nil { t.Fatalf("legacy %s: %v", name, err) } } for _, name := range []string{ ".", ".hostclaims", row.Host, row.Host + "/" + row.Host + ".db", row.Host + "/child", row.Host + "-other", row.Host + ".db-other", row.Host + ".db/", "./" + row.Host + ".db", "../" + row.Host + ".db", "/" + row.Host + ".db", filepath.ToSlash(filepath.Join(data, row.Host+".db")), "other.example.db", "other.example", "instances/other.example", "instances/other.example/other.example.db", "registry.db", "registry.db-wal", "registry.db-shm", "registry.db-journal", } { base.Entries[0].RelativePath = filepath.FromSlash(name) if err := ValidateAgentInventory(base, &row, data); !errors.Is(err, ErrTypedInventory) { t.Fatalf("legacy accepted foreign %s: %v", name, err) } } } func TestPlan9Fix2InventoryRegistryDBAtRootRefused(t *testing.T) { data := t.TempDir() row := registry.Route{Host: "app.example", ClaimOwnership: registry.ClaimOwnership{ClaimIncarnation: "inc", ClaimStamp: 1, ClaimOwner: "owner", ClaimRowID: "row"}} for _, dbPath := range []string{ filepath.Join(data, "app.example.db"), filepath.Join(data, "instances", "app.example", "app.example.db"), } { row.DBPath = dbPath for _, name := range []string{"registry.db", "registry.db-wal", "registry.db-shm", "registry.db-journal"} { in := StopInventory{Host: row.Host, Incarnation: "inc", InstallationStamp: 1, Entries: []StopInventoryEntry{{Kind: "path", ManagedRoot: "data", RelativePath: name, Ownership: "owner", Materialization: "materialized"}}} if err := ValidateAgentInventory(in, &row, data); !errors.Is(err, ErrTypedInventory) { t.Fatalf("db %s accepted %s: %v", dbPath, name, err) } } } row.Host = "registry" row.DBPath = filepath.Join(data, "registry.db") for _, name := range []string{"registry.db", "registry.db-wal", "registry.db-shm", "registry.db-journal"} { in := StopInventory{Host: row.Host, Incarnation: "inc", InstallationStamp: 1, Entries: []StopInventoryEntry{{Kind: "path", ManagedRoot: "data", RelativePath: name, Ownership: "owner", Materialization: "materialized"}}} if err := ValidateAgentInventory(in, &row, data); !errors.Is(err, ErrTypedInventory) { t.Fatalf("colliding host accepted %s: %v", name, err) } } } func TestPlan9Fix2AgentCannotChooseNamespaceThroughRegistryDBPath(t *testing.T) { data := t.TempDir() row := registry.Route{Host: "app.example", ClaimOwnership: registry.ClaimOwnership{ClaimIncarnation: "inc", ClaimStamp: 1, ClaimOwner: "owner", ClaimRowID: "row"}} for _, parent := range []string{"instances/other.example", ".parked/other", ".hostexec", "hostguard", ".hostclaims", ".instances", "registry.db-backup"} { t.Run(parent, func(t *testing.T) { row.DBPath = filepath.Join(data, filepath.FromSlash(parent), "app.example.db") in := StopInventory{Host: row.Host, Incarnation: "inc", InstallationStamp: 1, Entries: []StopInventoryEntry{{Kind: "path", ManagedRoot: "data", RelativePath: filepath.FromSlash(parent), Ownership: "owner", Materialization: "materialized"}}} if err := ValidateAgentInventory(in, &row, data); !errors.Is(err, ErrTypedInventory) { t.Fatalf("agent chose root namespace %s through DBPath: %v", parent, err) } }) } row.Host = "hostguard" row.DBPath = filepath.Join(data, "hostguard.db") in := StopInventory{Host: row.Host, Incarnation: "inc", InstallationStamp: 1, Entries: []StopInventoryEntry{{Kind: "path", ManagedRoot: "data", RelativePath: "hostguard", Ownership: "owner", Materialization: "materialized"}}} if err := ValidateAgentInventory(in, &row, data); !errors.Is(err, ErrTypedInventory) { t.Fatalf("flat hostname selected root hostguard namespace: %v", err) } row.Host = "instances" row.DBPath = filepath.Join(data, "instances.db") in.Host = row.Host in.Entries[0].RelativePath = "instances/other.example" if err := ValidateAgentInventory(in, &row, data); !errors.Is(err, ErrTypedInventory) { t.Fatalf("flat hostname selected shared instances namespace: %v", err) } }