| 1 | package hostbox |
| 2 | |
| 3 | import ( |
| 4 | "errors" |
| 5 | "path/filepath" |
| 6 | "testing" |
| 7 | |
| 8 | "github.com/carlosframework/platform/internal/registry" |
| 9 | ) |
| 10 | |
| 11 | func TestPlan9Fix2InventoryNamespace(t *testing.T) { |
| 12 | data := t.TempDir() |
| 13 | 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"}} |
| 14 | base := StopInventory{Host: row.Host, Incarnation: row.ClaimIncarnation, InstallationStamp: row.ClaimStamp, Complete: true} |
| 15 | for _, tc := range []struct { |
| 16 | name, kind, root, path, id string |
| 17 | want bool |
| 18 | }{ |
| 19 | {"state parent itself", "path", "data", "instances/app.example", "", true}, |
| 20 | {"child", "path", "data", "instances/app.example/app.example.db", "", true}, |
| 21 | {"nested child", "path", "data", "instances/app.example/sub/file", "", true}, |
| 22 | {"sibling host dir", "path", "data", "instances/other.example", "", false}, |
| 23 | {"sibling host file", "path", "data", "instances/other.example/other.example.db", "", false}, |
| 24 | {"prefix sibling child", "path", "data", "instances/app.example-other/app.example.db", "", false}, |
| 25 | {"not clean dot", "path", "data", "instances/app.example/./x", "", false}, |
| 26 | {"not clean double slash", "path", "data", "instances/app.example//x", "", false}, |
| 27 | {"not clean trailing slash", "path", "data", "instances/app.example/x/", "", false}, |
| 28 | {"traversal back in", "path", "data", "instances/app.example/../app.example/x", "", false}, |
| 29 | {"traversal out", "path", "data", "../x", "", false}, |
| 30 | {"absolute", "path", "data", "/instances/app.example/x", "", false}, |
| 31 | {"absolute data path", "path", "data", filepath.ToSlash(filepath.Join(data, "instances", "app.example", "x")), "", false}, |
| 32 | {"root registry db", "path", "data", "registry.db", "", false}, |
| 33 | {"root registry db-wal", "path", "data", "registry.db-wal", "", false}, |
| 34 | {"root registry db-shm", "path", "data", "registry.db-shm", "", false}, |
| 35 | {"root registry db-journal", "path", "data", "registry.db-journal", "", false}, |
| 36 | {"flat db name under nested", "path", "data", "app.example.db", "", false}, |
| 37 | {"route", "route", "registry", "app.example", "row", true}, |
| 38 | {"timer", "timer", "registry", "app.example/daily", "timer", true}, |
| 39 | {"foreign state", "path", "data", "instances/other.example", "", false}, |
| 40 | {"prefix sibling", "path", "data", "instances/app.example-other", "", false}, |
| 41 | {"data root", "path", "data", ".", "", false}, |
| 42 | {"escape", "path", "data", "instances/app.example/../other.example", "", false}, |
| 43 | {"foreign route", "route", "registry", "other.example", "row", false}, |
| 44 | {"foreign row id", "route", "registry", "app.example", "other-row", false}, |
| 45 | {"foreign timer", "timer", "registry", "other.example/daily", "timer", false}, |
| 46 | {"unit", "path", "units", "carlos-edge.service.d/guard.conf", "", false}, |
| 47 | {"binary", "path", "opt", "carlos", "", false}, |
| 48 | {"unknown kind", "unit-state", "data", "instances/app.example", "", false}, |
| 49 | } { |
| 50 | t.Run(tc.name, func(t *testing.T) { |
| 51 | in := base |
| 52 | in.Entries = []StopInventoryEntry{{Kind: tc.kind, ManagedRoot: tc.root, RelativePath: filepath.FromSlash(tc.path), RowIdentity: tc.id, Ownership: "owner", Materialization: "materialized"}} |
| 53 | err := ValidateAgentInventory(in, &row, data) |
| 54 | if (err == nil) != tc.want { |
| 55 | t.Fatalf("allowed=%v: %v", tc.want, err) |
| 56 | } |
| 57 | if !tc.want && !errors.Is(err, ErrTypedInventory) { |
| 58 | t.Fatalf("rejection is not ErrTypedInventory: %v", err) |
| 59 | } |
| 60 | }) |
| 61 | } |
| 62 | // A missing row can record the initial route intent, but grants no path authority. |
| 63 | base.Entries = []StopInventoryEntry{{Kind: "route", ManagedRoot: "registry", RelativePath: row.Host, RowIdentity: "new-row", Ownership: "owner", Materialization: "intended"}} |
| 64 | if err := ValidateAgentInventory(base, nil, data); err != nil { |
| 65 | t.Fatal(err) |
| 66 | } |
| 67 | base.Entries[0] = StopInventoryEntry{Kind: "path", ManagedRoot: "data", RelativePath: "instances/app.example", Ownership: "owner", Materialization: "intended"} |
| 68 | if err := ValidateAgentInventory(base, nil, data); err == nil { |
| 69 | t.Fatal("missing row granted path authority") |
| 70 | } |
| 71 | row.DBPath = filepath.Join(data, row.Host+".db") |
| 72 | for _, name := range []string{row.Host + ".db", row.Host + ".db-wal", row.Host + ".db-shm", row.Host + ".db-journal"} { |
| 73 | base.Entries[0].RelativePath = name |
| 74 | if err := ValidateAgentInventory(base, &row, data); err != nil { |
| 75 | t.Fatalf("legacy %s: %v", name, err) |
| 76 | } |
| 77 | } |
| 78 | for _, name := range []string{ |
| 79 | ".", ".hostclaims", |
| 80 | row.Host, row.Host + "/" + row.Host + ".db", row.Host + "/child", |
| 81 | row.Host + "-other", row.Host + ".db-other", row.Host + ".db/", "./" + row.Host + ".db", |
| 82 | "../" + row.Host + ".db", "/" + row.Host + ".db", filepath.ToSlash(filepath.Join(data, row.Host+".db")), |
| 83 | "other.example.db", "other.example", "instances/other.example", "instances/other.example/other.example.db", |
| 84 | "registry.db", "registry.db-wal", "registry.db-shm", "registry.db-journal", |
| 85 | } { |
| 86 | base.Entries[0].RelativePath = filepath.FromSlash(name) |
| 87 | if err := ValidateAgentInventory(base, &row, data); !errors.Is(err, ErrTypedInventory) { |
| 88 | t.Fatalf("legacy accepted foreign %s: %v", name, err) |
| 89 | } |
| 90 | } |
| 91 | } |
| 92 | |
| 93 | func TestPlan9Fix2InventoryRegistryDBAtRootRefused(t *testing.T) { |
| 94 | data := t.TempDir() |
| 95 | row := registry.Route{Host: "app.example", ClaimOwnership: registry.ClaimOwnership{ClaimIncarnation: "inc", ClaimStamp: 1, ClaimOwner: "owner", ClaimRowID: "row"}} |
| 96 | for _, dbPath := range []string{ |
| 97 | filepath.Join(data, "app.example.db"), |
| 98 | filepath.Join(data, "instances", "app.example", "app.example.db"), |
| 99 | } { |
| 100 | row.DBPath = dbPath |
| 101 | for _, name := range []string{"registry.db", "registry.db-wal", "registry.db-shm", "registry.db-journal"} { |
| 102 | in := StopInventory{Host: row.Host, Incarnation: "inc", InstallationStamp: 1, Entries: []StopInventoryEntry{{Kind: "path", ManagedRoot: "data", RelativePath: name, Ownership: "owner", Materialization: "materialized"}}} |
| 103 | if err := ValidateAgentInventory(in, &row, data); !errors.Is(err, ErrTypedInventory) { |
| 104 | t.Fatalf("db %s accepted %s: %v", dbPath, name, err) |
| 105 | } |
| 106 | } |
| 107 | } |
| 108 | row.Host = "registry" |
| 109 | row.DBPath = filepath.Join(data, "registry.db") |
| 110 | for _, name := range []string{"registry.db", "registry.db-wal", "registry.db-shm", "registry.db-journal"} { |
| 111 | in := StopInventory{Host: row.Host, Incarnation: "inc", InstallationStamp: 1, Entries: []StopInventoryEntry{{Kind: "path", ManagedRoot: "data", RelativePath: name, Ownership: "owner", Materialization: "materialized"}}} |
| 112 | if err := ValidateAgentInventory(in, &row, data); !errors.Is(err, ErrTypedInventory) { |
| 113 | t.Fatalf("colliding host accepted %s: %v", name, err) |
| 114 | } |
| 115 | } |
| 116 | } |
| 117 | |
| 118 | func TestPlan9Fix2AgentCannotChooseNamespaceThroughRegistryDBPath(t *testing.T) { |
| 119 | data := t.TempDir() |
| 120 | row := registry.Route{Host: "app.example", ClaimOwnership: registry.ClaimOwnership{ClaimIncarnation: "inc", ClaimStamp: 1, ClaimOwner: "owner", ClaimRowID: "row"}} |
| 121 | for _, parent := range []string{"instances/other.example", ".parked/other", ".hostexec", "hostguard", ".hostclaims", ".instances", "registry.db-backup"} { |
| 122 | t.Run(parent, func(t *testing.T) { |
| 123 | row.DBPath = filepath.Join(data, filepath.FromSlash(parent), "app.example.db") |
| 124 | in := StopInventory{Host: row.Host, Incarnation: "inc", InstallationStamp: 1, Entries: []StopInventoryEntry{{Kind: "path", ManagedRoot: "data", RelativePath: filepath.FromSlash(parent), Ownership: "owner", Materialization: "materialized"}}} |
| 125 | if err := ValidateAgentInventory(in, &row, data); !errors.Is(err, ErrTypedInventory) { |
| 126 | t.Fatalf("agent chose root namespace %s through DBPath: %v", parent, err) |
| 127 | } |
| 128 | }) |
| 129 | } |
| 130 | row.Host = "hostguard" |
| 131 | row.DBPath = filepath.Join(data, "hostguard.db") |
| 132 | in := StopInventory{Host: row.Host, Incarnation: "inc", InstallationStamp: 1, Entries: []StopInventoryEntry{{Kind: "path", ManagedRoot: "data", RelativePath: "hostguard", Ownership: "owner", Materialization: "materialized"}}} |
| 133 | if err := ValidateAgentInventory(in, &row, data); !errors.Is(err, ErrTypedInventory) { |
| 134 | t.Fatalf("flat hostname selected root hostguard namespace: %v", err) |
| 135 | } |
| 136 | row.Host = "instances" |
| 137 | row.DBPath = filepath.Join(data, "instances.db") |
| 138 | in.Host = row.Host |
| 139 | in.Entries[0].RelativePath = "instances/other.example" |
| 140 | if err := ValidateAgentInventory(in, &row, data); !errors.Is(err, ErrTypedInventory) { |
| 141 | t.Fatalf("flat hostname selected shared instances namespace: %v", err) |
| 142 | } |
| 143 | } |
| 144 | |