carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1package host
2
3import (
4 "context"
5 "os"
6 "path/filepath"
7 "testing"
8 "time"
9
10 "github.com/carlosframework/platform/internal/release"
11 "github.com/carlosframework/platform/internal/rootstate"
12)
13
14func TestPlan14GenerationDeletionCannotUnpin(t *testing.T) {
15 for _, attack := range []string{"delete", "empty", "rename", "missing-completeness", "deleted-executable", "renamed-parent", "registry-deleted", "wrong-root", "sync-failure"} {
16 t.Run(attack, func(t *testing.T) {
17 f, disk, _ := generationLaunchFixture(t)
18 permit, err := f.root.generations.reserve(t.Context(), f.root, f.peer, f.req)
19 if err != nil {
20 t.Fatal(err)
21 }
22 b := permit.reservation.Binding
23 opt := f.root.generations.optDir
24 stageSweepVersion(t, opt, "bap", "v2", "other", 48*time.Hour)
25 stageSweepVersion(t, opt, "bap", "v3", "pending", 48*time.Hour)
26 pending := b
27 pending.Attempt, pending.Invocation = "pending-v3", "pending-v3"
28 pending.Binding.Account, pending.Binding.App, pending.Version = "bap", "pending", "v3"
29 pending.Path = release.VersionPath(opt, "bap", "v3", "pending")
30 pending.Tree = filepath.Dir(pending.Path)
31 for path, dst := range map[string]*rootstate.FileIdentity{pending.Path: &pending.Executable, pending.Tree: &pending.TreeIdentity} {
32 file, err := os.Open(path)
33 if err != nil {
34 t.Fatal(err)
35 }
36 *dst, err = rootstate.Identity(file)
37 file.Close()
38 if err != nil {
39 t.Fatal(err)
40 }
41 }
42 if _, err := permit.ledger.Reserve(pending); err != nil {
43 t.Fatal(err)
44 }
45 when := time.Now().Add(-48 * time.Hour)
46 if err := os.Chtimes(b.Path, when, when); err != nil {
47 t.Fatal(err)
48 }
49 opened, err := os.Open(b.Path)
50 if err != nil {
51 t.Fatal(err)
52 }
53 defer opened.Close()
54 before, err := opened.Stat()
55 if err != nil {
56 t.Fatal(err)
57 }
58 dir := t.TempDir()
59 path := release.GenerationsPath(dir)
60 if err := release.WriteGenerations(path, release.Generations{Paths: map[string][]string{b.Binding.Host: {b.Path}}}); err != nil {
61 t.Fatal(err)
62 }
63 source := generationPinSource{open: func() (rootstate.Store, error) { return disk, nil }, verify: func(context.Context, []release.GenerationReservation) error { return nil }}
64 switch attack {
65 case "empty":
66 if err := release.WriteGenerations(path, release.Generations{}); err != nil {
67 t.Fatal(err)
68 }
69 case "rename":
70 if err := os.Rename(filepath.Dir(path), filepath.Dir(path)+".old"); err != nil {
71 t.Fatal(err)
72 }
73 default:
74 if err := os.Remove(path); err != nil {
75 t.Fatal(err)
76 }
77 if err := os.Remove(release.GenerationsSeqPath(path)); err != nil {
78 t.Fatal(err)
79 }
80 }
81 switch attack {
82 case "missing-completeness":
83 source.verify = nil
84 case "deleted-executable":
85 if err := os.Remove(b.Path); err != nil {
86 t.Fatal(err)
87 }
88 case "renamed-parent":
89 if err := os.Rename(b.Tree, b.Tree+".old"); err != nil {
90 t.Fatal(err)
91 }
92 case "wrong-root":
93 opt = t.TempDir()
94 stageSweepVersion(t, opt, "bab", "v1", "shop", 48*time.Hour)
95 case "sync-failure":
96 disk.fail = 1
97 }
98 // No registry row or live link carries the root reservation.
99 sweepStagedVersions(fakeLister{}, opt, path, 6*time.Hour, time.Now(), source)
100 if attack == "renamed-parent" {
101 if _, err := os.Stat(b.Tree + ".old"); err != nil {
102 t.Fatalf("renamed reserved tree removed: %v", err)
103 }
104 } else if !staged(t, opt, "v1") {
105 t.Fatal("sweep removed reserved version tree")
106 }
107 if attack != "wrong-root" && !staged(t, opt, "v3") {
108 t.Fatal("pending V3 lost its independent pin")
109 }
110 after, err := opened.Stat()
111 if err != nil || !os.SameFile(before, after) {
112 t.Fatalf("held inode changed: %v", err)
113 }
114 if attack == "delete" || attack == "empty" || attack == "rename" || attack == "registry-deleted" {
115 if staged(t, opt, "v2") {
116 t.Fatal("complete fixture could not sweep unrelated version")
117 }
118 } else if attack != "wrong-root" && !staged(t, opt, "v2") {
119 t.Fatal("incomplete snapshot permitted any deletion")
120 }
121 })
122 }
123}
124
125func TestPlan14GenerationConsumerMode(t *testing.T) {
126 for _, legacy := range []bool{true, false} {
127 calls := 0
128 source := generationPinSource{legacy: func() (bool, error) { return legacy, nil }, open: func() (rootstate.Store, error) { calls++; return nil, os.ErrPermission }}
129 _, err := source.StagedPins(t.Context(), t.TempDir())
130 if legacy && (err != nil || calls != 0) {
131 t.Fatalf("legacy read root generations: %v calls %d", err, calls)
132 }
133 if !legacy && (err == nil || calls != 1) {
134 t.Fatalf("protected fallback: %v calls %d", err, calls)
135 }
136 }
137 source := generationPinSource{legacy: func() (bool, error) { return true, os.ErrPermission }}
138 if _, err := source.StagedPins(t.Context(), t.TempDir()); err == nil {
139 t.Fatal("census error chose legacy")
140 }
141}
142
143func TestPlan14GenerationProductionModeGate(t *testing.T) {
144 for _, flag := range []string{"", "1"} {
145 t.Run(flag, func(t *testing.T) {
146 t.Setenv("CARLOS_HOST_CLAIM", flag)
147 source := productionRootGenerationPins().(generationPinSource)
148 if source.legacy != nil {
149 source.legacy = func() (bool, error) { return true, nil }
150 }
151 calls := 0
152 source.open = func() (rootstate.Store, error) { calls++; return nil, os.ErrPermission }
153 opt := t.TempDir()
154 stageSweepVersion(t, opt, "bab", "v1", "shop", 48*time.Hour)
155 sweepStagedVersions(fakeLister{}, opt, noGenerations(t), 6*time.Hour, time.Now(), source)
156 if calls != 0 || staged(t, opt, "v1") {
157 t.Fatalf("no-census sweep changed: opens=%d survives=%v", calls, staged(t, opt, "v1"))
158 }
159 })
160 }
161}
162